cloudlb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/COPYING +12 -0
- data/README.rdoc +66 -0
- data/VERSION +1 -0
- data/cloudlb.gemspec +38 -0
- data/lib/cloudlb.rb +98 -0
- data/lib/cloudlb/authentication.rb +44 -0
- data/lib/cloudlb/balancer.rb +233 -0
- data/lib/cloudlb/connection.rb +199 -0
- data/lib/cloudlb/connection_throttle.rb +105 -0
- data/lib/cloudlb/exception.rb +65 -0
- data/lib/cloudlb/health_monitor.rb +135 -0
- data/lib/cloudlb/node.rb +64 -0
- metadata +109 -0
data/.gitignore
ADDED
@@ -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
|
+
|
data/README.rdoc
ADDED
@@ -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
|
data/cloudlb.gemspec
ADDED
@@ -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
|
data/lib/cloudlb.rb
ADDED
@@ -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
|
data/lib/cloudlb/node.rb
ADDED
@@ -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
|
+
|