awsborn 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.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