awsborn 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.mdown CHANGED
@@ -77,12 +77,12 @@ equally well be added as options to the specific servers.
77
77
 
78
78
  ### Defining a cluster
79
79
 
80
- The `cluster` method accepts two commands, `domain` and `server`. `domain` is
81
- optional and is just a way to avoid repetition in the `server` commands. `server`
80
+ The `cluster` method accepts three commands, `domain`, `load_balancer` and `server`. `domain` is
81
+ optional and is just a way to avoid repetition in the `server` commands. `load_balancer` is also optional and take a name and a hash. `server`
82
82
  takes a name (which can be used as a key in the cluster, e.g. `log_servers[:log_a]`)
83
83
  and a hash:
84
84
 
85
- Mandatory keys:
85
+ Mandatory keys for server:
86
86
 
87
87
  * `:zone` -- the availability zone for the server. One of `:us_east_1a`, `:us_east_1b`,
88
88
  `:us_east_1c`, `:us_east_1d`, `:us_west_1a`, `:us_west_1b`, `:eu_west_1a`, `:eu_west_1b`,
@@ -92,7 +92,7 @@ Mandatory keys:
92
92
  `[volume-id, :format]`, in which case `the_server.format_disk_on_device?(device)`
93
93
  will return `true`. See `contrib/cookbooks/ec2-ebs` for example usage.
94
94
 
95
- Optional keys:
95
+ Optional keys for server:
96
96
 
97
97
  * `:ip` -- a domain name which translates to an elastic ip. If the domain name does not
98
98
  contain a full stop (dot) and `domain` has been specified above, the domain is added.
@@ -106,6 +106,40 @@ Keys that override server type settings:
106
106
  * `:bootstrap_script`
107
107
  * `:image_id`
108
108
 
109
+ Mandatory keys for load_balancer:
110
+
111
+ * `:region` -- the region covered by the load balancer. One of `:us_east_1`, `:us_west_1`, `:eu_west_1`, `:ap_southeast_1`
112
+
113
+ Optional keys for load_balancer:
114
+
115
+ * `:only` -- A list of server names to which the load balancer is
116
+ restricted
117
+ * `:except` -- A list of servers that the load balancer should ignore
118
+ * `:listerners` -- A list of listeners definitions for the load
119
+ balancer. For instance: `{ :protocol => :http, :load_balancer_port => 80, :instance_port => 80 }`
120
+ * `:sticky_cookies` -- A list of sticky cookies policies that the load balancer
121
+ should take care of. There are three kinds of available policies: `:disabled`, `:app_generated` (the cookie is generated by the app) or `:lb_generated` (the cookie is generated by the load balancer).
122
+
123
+ Example:
124
+
125
+ cluster "test" do
126
+ domain 'yourdimain.net'
127
+ load_balancer 'test-balancer',
128
+ :region => :eu_west_1,
129
+ :only => [:cluster_test_1, :cluster_test_2],
130
+ :listeners => [
131
+ { :protocol => :http, :load_balancer_port => 80, :instance_port => 80},
132
+ { :protocol => :tcp, :load_balancer_port => 443, :instance_port => 443}
133
+ ],
134
+ :sticky_cookies => [
135
+ { :ports => [80], :policy => :disabled }
136
+ #{ :ports => [80], :policy => :app_generated, :cookie_name => 'some_cookie' }
137
+ #{ :ports => [80], :policy => :lb_generated, :expiration_period => 10 * 60 }
138
+ ]
139
+ server :cluster_test_1, :zone => :eu_west_1a, :disk => {:sdf => "vol-12345678"}, :instance_type => :m1_small
140
+ server :cluster_test_2, :zone => :eu_west_1a, :disk => {:sdf => "vol-09876543"}, :instance_type => :m1_small
141
+ end
142
+
109
143
  ### Launching a cluster
110
144
 
111
145
  The `launch` method on the cluster checks to see if each server is running by checking
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.0
1
+ 0.8.0
data/awsborn.gemspec ADDED
@@ -0,0 +1,80 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{awsborn}
8
+ s.version = "0.8.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["David Vrensk"]
12
+ s.date = %q{2011-08-11}
13
+ s.description = %q{Awsborn lets you define and launch a server cluster on Amazon EC2.}
14
+ s.email = %q{david@icehouse.se}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.mdown"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "LICENSE",
22
+ "README.mdown",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "awsborn.gemspec",
26
+ "contrib/chef-bootstrap.sh",
27
+ "contrib/cookbooks/ec2-ebs/recipes/default.rb",
28
+ "lib/awsborn.rb",
29
+ "lib/awsborn/aws_constants.rb",
30
+ "lib/awsborn/awsborn.rb",
31
+ "lib/awsborn/ec2.rb",
32
+ "lib/awsborn/elb.rb",
33
+ "lib/awsborn/extensions/object.rb",
34
+ "lib/awsborn/extensions/proc.rb",
35
+ "lib/awsborn/git_branch.rb",
36
+ "lib/awsborn/keychain.rb",
37
+ "lib/awsborn/known_hosts_updater.rb",
38
+ "lib/awsborn/load_balancer.rb",
39
+ "lib/awsborn/rake.rb",
40
+ "lib/awsborn/server.rb",
41
+ "lib/awsborn/server_cluster.rb",
42
+ "spec/aws_constants_spec.rb",
43
+ "spec/ec2_spec.rb",
44
+ "spec/elb_spec.rb",
45
+ "spec/load_balancer_spec.rb",
46
+ "spec/server_spec.rb",
47
+ "spec/spec.opts",
48
+ "spec/spec_helper.rb"
49
+ ]
50
+ s.homepage = %q{http://github.com/icehouse/awsborn}
51
+ s.require_paths = ["lib"]
52
+ s.rubygems_version = %q{1.3.7}
53
+ s.summary = %q{Awsborn defines servers as instances with a certain disk volume, which makes it easy to restart missing servers.}
54
+
55
+ if s.respond_to? :specification_version then
56
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
57
+ s.specification_version = 3
58
+
59
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
60
+ s.add_runtime_dependency(%q<right_aws>, [">= 2.1.0"])
61
+ s.add_runtime_dependency(%q<json_pure>, [">= 1.2.3"])
62
+ s.add_runtime_dependency(%q<rake>, [">= 0"])
63
+ s.add_development_dependency(%q<rspec>, [">= 2.6.0"])
64
+ s.add_development_dependency(%q<webmock>, [">= 1.3.0"])
65
+ else
66
+ s.add_dependency(%q<right_aws>, [">= 2.1.0"])
67
+ s.add_dependency(%q<json_pure>, [">= 1.2.3"])
68
+ s.add_dependency(%q<rake>, [">= 0"])
69
+ s.add_dependency(%q<rspec>, [">= 2.6.0"])
70
+ s.add_dependency(%q<webmock>, [">= 1.3.0"])
71
+ end
72
+ else
73
+ s.add_dependency(%q<right_aws>, [">= 2.1.0"])
74
+ s.add_dependency(%q<json_pure>, [">= 1.2.3"])
75
+ s.add_dependency(%q<rake>, [">= 0"])
76
+ s.add_dependency(%q<rspec>, [">= 2.6.0"])
77
+ s.add_dependency(%q<webmock>, [">= 1.3.0"])
78
+ end
79
+ end
80
+
@@ -0,0 +1,59 @@
1
+ module Awsborn
2
+ module AwsConstants
3
+
4
+ AVAILABILITY_ZONES = %w[
5
+ us-east-1a us-east-1b us-east-1c us-east-1d
6
+ us-west-1a us-west-1b
7
+ eu-west-1a eu-west-1b
8
+ ap-southeast-1a ap-southeast-1b
9
+ ap-northeast-1a ap-northeast-1b
10
+ ]
11
+ REGIONS = AVAILABILITY_ZONES.map{|z| z.sub(/[a-z]$/,'') }.uniq
12
+ INSTANCE_TYPES_32_BIT = %w[m1.small c1.medium t1.micro]
13
+ INSTANCE_TYPES_64_BIT = %w[m1.large m1.xlarge m2.xlarge m2.2xlarge m2.4xlarge c1.xlarge cc1.4xlarge t1.micro]
14
+ INSTANCE_TYPES = (INSTANCE_TYPES_32_BIT + INSTANCE_TYPES_64_BIT).uniq
15
+ SYMBOL_CONSTANT_MAP = (AVAILABILITY_ZONES + INSTANCE_TYPES).inject({}) { |memo,str| memo[str.tr('-.','_').to_sym] = str; memo }
16
+
17
+ def endpoint_for_zone_and_service (zone, service)
18
+ region = zone_to_awz_region(zone)
19
+ case service
20
+ when :ec2 then "https://#{region}.ec2.amazonaws.com"
21
+ when :elb then "https://#{region}.elasticloadbalancing.amazonaws.com"
22
+ end
23
+ end
24
+
25
+ def zone_to_awz_region (zone)
26
+ region = zone.to_s.sub(/[a-z]$/,'').tr('_','-')
27
+ raise UnknownConstantError, "Unknown region: #{region} for zone: #{zone}" unless REGIONS.include? region
28
+ region
29
+ end
30
+
31
+ def symbol_to_aws_zone (symbol)
32
+ zone = symbol.to_s.tr('_','-')
33
+ raise UnknownConstantError, "Unknown availability zone: #{zone}" unless AVAILABILITY_ZONES.include? zone
34
+ zone
35
+ end
36
+
37
+ def aws_zone_to_symbol (zone)
38
+ raise UnknownConstantError, "Unknown availability zone: #{zone}" unless AVAILABILITY_ZONES.include? zone
39
+ zone.to_s.tr('-','_').to_sym
40
+ end
41
+
42
+ def symbol_to_aws_instance_type (symbol)
43
+ type = symbol.to_s.tr('_','.')
44
+ raise UnknownConstantError, "Unknown instance type: #{type}" unless INSTANCE_TYPES.include? type
45
+ type
46
+ end
47
+
48
+ def aws_instance_type_to_symbol (type)
49
+ raise UnknownConstantError, "Unknown instance type: #{type}" unless INSTANCE_TYPES.include? type
50
+ type.to_s.tr('.','_').to_sym
51
+ end
52
+
53
+ def awz_constant (symbol)
54
+ SYMBOL_CONSTANT_MAP[symbol] || raise(UnknownConstantError, "Unknown constant: #{symbol}")
55
+ end
56
+ end
57
+
58
+ class UnknownConstantError < Exception; end
59
+ end
data/lib/awsborn/ec2.rb CHANGED
@@ -1,17 +1,11 @@
1
1
  module Awsborn
2
2
  class Ec2
3
3
  extend Forwardable
4
+ include Awsborn::AwsConstants
4
5
  def_delegators :Awsborn, :logger
5
6
 
6
7
  attr_accessor :instance_id
7
8
 
8
- class << self
9
- def endpoint_for_zone (zone)
10
- zone = zone.to_s.sub(/[a-z]$/,'').tr('_','-')
11
- "https://#{zone}.ec2.amazonaws.com"
12
- end
13
- end
14
-
15
9
  def connection
16
10
  unless @connection
17
11
  env_ec2_url = ENV['EC2_URL']
@@ -28,7 +22,7 @@ module Awsborn
28
22
  end
29
23
 
30
24
  def initialize (zone)
31
- @endpoint = self.class.endpoint_for_zone(zone)
25
+ @endpoint = endpoint_for_zone_and_service(zone, :ec2)
32
26
  end
33
27
 
34
28
  KeyPair = Struct.new :name, :path
@@ -0,0 +1,127 @@
1
+ module Awsborn
2
+ class Elb
3
+ extend Forwardable
4
+ def_delegators :Awsborn, :logger
5
+ include Awsborn::AwsConstants
6
+ attr_accessor :region, :endpoint
7
+
8
+ def connection
9
+ unless @connection
10
+ env_ec2_url = ENV['ELB_URL']
11
+ begin
12
+ ENV['ELB_URL'] = @endpoint
13
+ @connection = RightAws::ElbInterface.new(
14
+ Awsborn.access_key_id,
15
+ Awsborn.secret_access_key,
16
+ :logger => Awsborn.logger
17
+ )
18
+ ensure
19
+ ENV['ELB_URL'] = env_ec2_url
20
+ end
21
+ end
22
+ @connection
23
+ end
24
+
25
+ def initialize (zone)
26
+ @region = zone_to_awz_region(zone)
27
+ @endpoint = endpoint_for_zone_and_service(@region, :elb)
28
+ end
29
+
30
+ def describe_load_balancer (balancer_name)
31
+ connection.describe_load_balancers(balancer_name).first
32
+ end
33
+
34
+ def running? (balancer_name)
35
+ describe_load_balancer(balancer_name)
36
+ true
37
+ rescue RightAws::AwsError
38
+ false
39
+ end
40
+
41
+ def dns_name (balancer_name)
42
+ describe_load_balancer(balancer_name)[:dns_name]
43
+ end
44
+
45
+ def instances (balancer_name)
46
+ describe_load_balancer(balancer_name)[:instances]
47
+ end
48
+
49
+ def zones (balancer_name)
50
+ describe_load_balancer(balancer_name)[:availability_zones]
51
+ end
52
+
53
+ def create_load_balancer (balancer_name)
54
+ logger.debug "Creating load balancer #{balancer_name}"
55
+ connection.create_load_balancer(balancer_name, [@region+'a'], [])
56
+ end
57
+
58
+ def set_load_balancer_listeners (balancer_name, listeners)
59
+ logger.debug "Setting listeners on load balancer #{balancer_name}"
60
+ description = describe_load_balancer(balancer_name)
61
+ previous_ports = description[:listeners].map{|i| i[:instance_port]}
62
+ connection.delete_load_balancer_listeners(balancer_name, *previous_ports) unless previous_ports.empty?
63
+ connection.create_load_balancer_listeners(balancer_name, listeners)
64
+ end
65
+
66
+ def set_lb_cookie_policy (balancer_name, ports, expiration_period)
67
+ logger.debug "Setting cookie policies for ports #{ports.inspect} on load balancer #{balancer_name}"
68
+ policy_name = "lb-#{balancer_name}-#{expiration_period}".tr('_','-')
69
+ unless existing_lb_cookie_policies(balancer_name).include?(policy_name)
70
+ connection.create_lb_cookie_stickiness_policy(balancer_name, policy_name, expiration_period)
71
+ end
72
+ ports.each do |port|
73
+ connection.set_load_balancer_policies_of_listener(balancer_name, port, policy_name)
74
+ end
75
+ end
76
+
77
+ def set_app_cookie_policy (balancer_name, ports, cookie_name)
78
+ logger.debug "Setting cookie policies for ports #{ports.inspect} on load balancer #{balancer_name}"
79
+ policy_name = "app-#{balancer_name}-#{cookie_name}".tr('_','-')
80
+ unless existing_app_cookie_policies(balancer_name).include?(policy_name)
81
+ connection.create_app_cookie_stickiness_policy(balancer_name, policy_name, cookie_name)
82
+ end
83
+ ports.each do |port|
84
+ connection.set_load_balancer_policies_of_listener(balancer_name, port, policy_name)
85
+ end
86
+ end
87
+
88
+ def remove_all_cookie_policies(balancer_name)
89
+ description = describe_load_balancer(balancer_name)
90
+ description[:listeners].each do |listener|
91
+ connection.set_load_balancer_policies_of_listener(balancer_name, listener[:load_balancer_port])
92
+ end
93
+ end
94
+
95
+ def register_instances (balancer_name, instances)
96
+ logger.debug "Registering instances #{instances.inspect} on load balancer #{balancer_name}"
97
+ connection.register_instances_with_load_balancer(balancer_name, *instances)
98
+ end
99
+
100
+ def deregister_instances (balancer_name, instances)
101
+ logger.debug "De-registering instances #{instances.inspect} on load balancer #{balancer_name}"
102
+ connection.deregister_instances_with_load_balancer(balancer_name, *instances)
103
+ end
104
+
105
+ def enable_zones (balancer_name, zones)
106
+ logger.debug "Enabling zones #{zones.inspect} on load balancer #{balancer_name}"
107
+ connection.enable_availability_zones_for_load_balancer(balancer_name, *zones)
108
+ end
109
+
110
+ def disable_zones (balancer_name, zones)
111
+ logger.debug "Disabling zones #{zones.inspect} on load balancer #{balancer_name}"
112
+ connection.disable_availability_zones_for_load_balancer(balancer_name, *zones)
113
+ end
114
+
115
+ protected
116
+
117
+ def existing_app_cookie_policies (balancer_name)
118
+ description = describe_load_balancer(balancer_name)
119
+ description[:app_cookie_stickiness_policies].map {|p| p[:policy_name]}.uniq
120
+ end
121
+
122
+ def existing_lb_cookie_policies (balancer_name)
123
+ description = describe_load_balancer(balancer_name)
124
+ description[:lb_cookie_stickiness_policies].map {|p| p[:policy_name]}.uniq
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,132 @@
1
+ module Awsborn
2
+ class LoadBalancer
3
+
4
+ include Awsborn::AwsConstants
5
+ attr_accessor :name, :only, :except, :region, :listeners, :sticky_cookies
6
+
7
+ DEFAULT_LISTENERS = [ { :protocol => :http, :load_balancer_port => 80, :instance_port => 80} ]
8
+ def initialize (name, options={})
9
+ @name = name
10
+ @only = options[:only] || []
11
+ @except = options[:except] || []
12
+ @region = zone_to_awz_region(options[:region])
13
+ @listeners = options[:listeners] || DEFAULT_LISTENERS
14
+ @sticky_cookies = options[:sticky_cookies] || []
15
+ launch unless running?
16
+ end
17
+
18
+ def elb
19
+ @elb ||= Elb.new(@region)
20
+ end
21
+
22
+ def dns_name
23
+ elb.dns_name(@name)
24
+ end
25
+
26
+ def instances
27
+ elb.instances(@name)
28
+ end
29
+
30
+ def instances= (new_instances)
31
+ previous_instances = self.instances
32
+ register_instances(new_instances - previous_instances)
33
+ deregister_instances(previous_instances - new_instances)
34
+ self.instances
35
+ end
36
+
37
+ def zones
38
+ elb.zones(@name)
39
+ end
40
+
41
+ def zones= (new_zones)
42
+ previous_zones = self.zones
43
+ enable_zones(new_zones - previous_zones)
44
+ disable_zones(previous_zones - new_zones)
45
+ self.zones
46
+ end
47
+
48
+ def description
49
+ elb.describe_load_balancer(@name)
50
+ end
51
+
52
+ def running?
53
+ elb.running?(@name)
54
+ end
55
+
56
+ def launch
57
+ elb.create_load_balancer(@name)
58
+ end
59
+
60
+
61
+ def update_listeners
62
+ elb.set_load_balancer_listeners(@name, @listeners)
63
+ end
64
+
65
+ def update_sticky_cookies
66
+ elb.remove_all_cookie_policies(@name)
67
+ @sticky_cookies.each do |sc|
68
+ raise "Option :ports is missing for #{sc.inspect}" if sc[:ports].nil?
69
+
70
+ case sc[:policy]
71
+ when :disabled
72
+ set_disabled_cookie_policy(sc[:ports])
73
+ when :lb_generated
74
+ set_lb_cookie_policy(sc[:ports], sc[:expiration_period])
75
+ when :app_generated
76
+ set_app_cookie_policy(sc[:ports], sc[:cookie_name])
77
+ else
78
+ raise "unknown policy #{sc.inspect}. Accepted policies => :disabled, :lb_generated, :app_generated"
79
+ end
80
+ end
81
+ end
82
+
83
+ def update_with (new_servers)
84
+ servers_to_be_balanced = new_servers
85
+ servers_to_be_balanced =
86
+ servers_to_be_balanced.select{|s| @only.include?(s.name)} unless @only.empty?
87
+ servers_to_be_balanced =
88
+ servers_to_be_balanced.reject{|s| @except.include?(s.name)} unless @except.empty?
89
+
90
+ self.instances = servers_to_be_balanced.map{|s| s.instance_id }
91
+ self.zones = servers_to_be_balanced.map{|s| symbol_to_aws_zone(s.zone) }.uniq
92
+
93
+ update_listeners
94
+ update_sticky_cookies
95
+
96
+ self.description
97
+ end
98
+
99
+ protected
100
+
101
+ def set_disabled_cookie_policy(ports)
102
+ # Do nothing
103
+ end
104
+
105
+ def set_app_cookie_policy(ports, cookie_name)
106
+ raise ":cookie_name is missing" if cookie_name.nil?
107
+ elb.set_app_sticky_cookie(@name, ports, cookie_name)
108
+ end
109
+
110
+ def set_lb_cookie_policy(ports, expiration_period)
111
+ raise ":expiration_period is missing" if expiration_period.nil?
112
+ elb.set_lb_sticky_cookie(@name, ports, expiration_period.to_i)
113
+ end
114
+
115
+ def register_instances (instances)
116
+ elb.register_instances(@name, instances) unless instances.empty?
117
+ end
118
+
119
+ def deregister_instances (instances)
120
+ elb.deregister_instances(@name, instances) unless instances.empty?
121
+ end
122
+
123
+ def enable_zones (zones)
124
+ elb.enable_zones(@name, zones) unless zones.empty?
125
+ end
126
+
127
+ def disable_zones (zones)
128
+ elb.disable_zones(@name, zones) unless zones.empty?
129
+ end
130
+
131
+ end
132
+ end