dew 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +38 -0
- data/Rakefile +26 -0
- data/bin/dew +87 -0
- data/config/cucumber.yaml +4 -0
- data/features/create-ami.feature +16 -0
- data/features/create-environments.feature +46 -0
- data/features/deploy-puge.feature +16 -0
- data/features/step_definitions/aws-steps.rb +101 -0
- data/features/step_definitions/deploy-puge-steps.rb +27 -0
- data/features/support/env.rb +38 -0
- data/features/support/hooks.rb +10 -0
- data/lib/dew.rb +7 -0
- data/lib/dew/aws_resources.yaml +122 -0
- data/lib/dew/base_command.rb +24 -0
- data/lib/dew/cloud.rb +79 -0
- data/lib/dew/commands.rb +6 -0
- data/lib/dew/commands/ami.rb +67 -0
- data/lib/dew/commands/console.rb +17 -0
- data/lib/dew/commands/console/irb_override.rb +24 -0
- data/lib/dew/commands/deploy.rb +114 -0
- data/lib/dew/commands/deploy/templates/apache.conf.erb +28 -0
- data/lib/dew/commands/deploy/templates/known_hosts +2 -0
- data/lib/dew/commands/deploy/templates/rvmrc +2 -0
- data/lib/dew/commands/environments.rb +110 -0
- data/lib/dew/commands/tidy.rb +35 -0
- data/lib/dew/controllers.rb +3 -0
- data/lib/dew/controllers/amis_controller.rb +82 -0
- data/lib/dew/controllers/deploy_controller.rb +10 -0
- data/lib/dew/controllers/environments_controller.rb +48 -0
- data/lib/dew/models.rb +7 -0
- data/lib/dew/models/account.rb +30 -0
- data/lib/dew/models/database.rb +32 -0
- data/lib/dew/models/deploy.rb +2 -0
- data/lib/dew/models/deploy/puge.rb +61 -0
- data/lib/dew/models/deploy/run.rb +19 -0
- data/lib/dew/models/environment.rb +199 -0
- data/lib/dew/models/fog_model.rb +23 -0
- data/lib/dew/models/profile.rb +60 -0
- data/lib/dew/models/server.rb +134 -0
- data/lib/dew/password.rb +7 -0
- data/lib/dew/tasks/spec.rake +14 -0
- data/lib/dew/validations.rb +8 -0
- data/lib/dew/version.rb +3 -0
- data/lib/dew/view.rb +39 -0
- data/lib/tasks/spec.rake +14 -0
- data/spec/dew/cloud_spec.rb +90 -0
- data/spec/dew/controllers/amis_controller_spec.rb +137 -0
- data/spec/dew/controllers/deploy_controller_spec.rb +38 -0
- data/spec/dew/controllers/environments_controller_spec.rb +133 -0
- data/spec/dew/models/account_spec.rb +47 -0
- data/spec/dew/models/database_spec.rb +58 -0
- data/spec/dew/models/deploy/puge_spec.rb +72 -0
- data/spec/dew/models/deploy/run_spec.rb +38 -0
- data/spec/dew/models/environment_spec.rb +374 -0
- data/spec/dew/models/fog_model_spec.rb +24 -0
- data/spec/dew/models/profile_spec.rb +85 -0
- data/spec/dew/models/server_spec.rb +190 -0
- data/spec/dew/password_spec.rb +11 -0
- data/spec/dew/spec_helper.rb +22 -0
- data/spec/dew/view_spec.rb +38 -0
- metadata +284 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'deploy', 'puge'))
|
2
|
+
|
3
|
+
module Deploy
|
4
|
+
|
5
|
+
class Run
|
6
|
+
|
7
|
+
attr_reader :deploy_type, :environment, :opts
|
8
|
+
|
9
|
+
def initialize deploy_type, environment, opts
|
10
|
+
@deploy_type = deploy_type
|
11
|
+
@environment = environment
|
12
|
+
@opts = opts
|
13
|
+
end
|
14
|
+
|
15
|
+
def deploy
|
16
|
+
Deploy.const_get(deploy_type.capitalize).new(@environment.servers, @opts).deploy
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'dew/validations'
|
2
|
+
|
3
|
+
class Environment
|
4
|
+
include Validations
|
5
|
+
|
6
|
+
attr_reader :name, :servers, :database
|
7
|
+
|
8
|
+
def initialize name, servers=[], database=nil
|
9
|
+
@name = name
|
10
|
+
@servers = servers
|
11
|
+
@database = database
|
12
|
+
valid?
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid?
|
16
|
+
Validation::validates_format_of @name, /^[a-zA-Z0-9-]+$/
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.get name
|
20
|
+
servers = Server.find('Environment', name)
|
21
|
+
database = Database.get(name)
|
22
|
+
servers.length > 0 || database ? new(name, servers, database) : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.create(name, profile)
|
26
|
+
raise "Keypair '#{profile.keypair}' is not available in AWS #{Cloud.region}." unless Cloud.keypair_exists?(profile.keypair)
|
27
|
+
raise "AMI for '#{Cloud.region}' is not setup in the '#{profile.profile_name}' profile." unless profile.ami
|
28
|
+
|
29
|
+
environment = new(name)
|
30
|
+
Inform.info "Creating new environment %{name}", :name => name
|
31
|
+
|
32
|
+
# Creating the database is the longest running task, so do that first.
|
33
|
+
password = Password.random if profile.has_rds?
|
34
|
+
environment.add_database(profile.rds_size, password) if profile.has_rds?
|
35
|
+
|
36
|
+
(1..profile.count).each do
|
37
|
+
environment.add_server(profile.ami, profile.size, profile.keypair, profile.security_groups)
|
38
|
+
end
|
39
|
+
|
40
|
+
environment.add_elb(profile.elb_listener_ports) if profile.has_elb?
|
41
|
+
|
42
|
+
environment.wait_until_ready
|
43
|
+
|
44
|
+
environment.configure_servers_for_database password if profile.has_rds?
|
45
|
+
Inform.info "Environment %{name} ready!", :name => name
|
46
|
+
environment
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.owners
|
50
|
+
Cloud.valid_servers.collect(&:tags).collect { |h| {:name => h['Environment'], :owner => h['Creator']} if h['Environment'] }.uniq.compact
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.index
|
54
|
+
#environments = rows.inject({}) { |h, (name, owner)| h[name] = Environment.get(name); h }
|
55
|
+
#items = rows.collect { |name, owner| [name, owner, environments[name].servers.size, environments[name].database] }
|
56
|
+
#table(%w(name owner instance_count database), *rows).to_s.indent
|
57
|
+
Inform.info View.new('Environments', owners, %w(name owner)).index
|
58
|
+
end
|
59
|
+
|
60
|
+
def show
|
61
|
+
result = ""
|
62
|
+
|
63
|
+
result << "ENVIRONMENT:\n"
|
64
|
+
|
65
|
+
result << "NAME: #{name}\n"
|
66
|
+
result << "REGION: #{Cloud.region}\n"
|
67
|
+
result << "ACCOUNT: #{Cloud.account.aws_user_id}\n"
|
68
|
+
|
69
|
+
unless servers.empty?
|
70
|
+
#keys = %w(id flavor_id public_ip_address state created_at key_name tags)
|
71
|
+
keys = %w(id flavor_id public_ip_address state created_at groups key_name availability_zone creator)
|
72
|
+
servers_view = View.new('SERVERS', servers, keys)
|
73
|
+
result << servers_view.index
|
74
|
+
end
|
75
|
+
|
76
|
+
if elb
|
77
|
+
elbs_view = View.new('ELB', [elb], %w(created_at dns_name instances availability_zones))
|
78
|
+
result << elbs_view.show(0)
|
79
|
+
end
|
80
|
+
|
81
|
+
if database
|
82
|
+
databases_view = View.new('DATABASE', [database], %w(flavor_id state created_at availability_zone db_security_groups))
|
83
|
+
result << databases_view.show(0)
|
84
|
+
end
|
85
|
+
|
86
|
+
Inform.info result
|
87
|
+
end
|
88
|
+
|
89
|
+
def destroy
|
90
|
+
servers.each do |server|
|
91
|
+
Inform.info("Destroying server %{serverid}", :serverid => server.id) do
|
92
|
+
server.destroy
|
93
|
+
end
|
94
|
+
end
|
95
|
+
if database
|
96
|
+
Inform.info("Destroying database") do
|
97
|
+
database.destroy(nil)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
if has_elb?
|
101
|
+
Inform.info("Destroying load balancer") do
|
102
|
+
Cloud.elb.delete_load_balancer(name)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_server ami, size, keypair, groups
|
108
|
+
Inform.info "Adding server using AMI %{ami} of size %{size}, keypair %{keypair} and security groups %{groups}",
|
109
|
+
:ami => ami, :size => size, :keypair => keypair, :groups => groups do
|
110
|
+
server = Server.create!( ami, size, keypair, groups )
|
111
|
+
server.add_tag('Environment', name)
|
112
|
+
server.add_tag('Creator', ENV['USER'])
|
113
|
+
server_name = "#{name} #{servers.count + 1}"
|
114
|
+
server.add_tag('Name', server_name)
|
115
|
+
Inform.debug("%{name} ID: %{id} AZ: %{az}", :name => server_name, :id =>server.id, :az => server.availability_zone)
|
116
|
+
servers << server
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def add_server_to_elb server
|
121
|
+
Inform.debug("Adding %{id} to ELB", :id => server.id)
|
122
|
+
Cloud.elb.register_instances_with_load_balancer([server.id], name)
|
123
|
+
end
|
124
|
+
|
125
|
+
def remove_server_from_elb server
|
126
|
+
Inform.debug("Removing %{id} from ELB", :id => server.id)
|
127
|
+
Cloud.elb.deregister_instances_from_load_balancer([server.id], name)
|
128
|
+
end
|
129
|
+
|
130
|
+
def add_elb listener_ports=[]
|
131
|
+
zones = servers.map {|s| s.availability_zone}.uniq
|
132
|
+
Inform.info "Adding Load Balancer for availability zone(s) %{zones}", :zones => zones.join(', ') do
|
133
|
+
Cloud.elb.create_load_balancer(zones, name, listeners_from_ports(listener_ports))
|
134
|
+
Inform.debug("ELB created, adding instances...")
|
135
|
+
Cloud.elb.register_instances_with_load_balancer(servers.map {|s| s.id}, name)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def add_database size, password
|
140
|
+
Inform.info "Adding Database %{name} of size %{size} with master password %{password}",
|
141
|
+
:name => name, :size => size, :password => password do
|
142
|
+
@database = Database.create!(name, size, password)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def wait_until_ready
|
147
|
+
Inform.info "Waiting for servers to become ready" do
|
148
|
+
servers.each do |server|
|
149
|
+
ip_permissions = server.groups.map { |group_name| Cloud.security_groups[group_name] }.compact.collect(&:ip_permissions)
|
150
|
+
if ip_permissions.flatten.empty?
|
151
|
+
Inform.warning "Server %{id} has no ip_permissions in its security groups %{security_group_names}", :id => server.id, :security_group_names => server.groups.inspect
|
152
|
+
else
|
153
|
+
Inform.debug "Trying to connect to %{id} using ip_permissions %{ip_permissions}", :id => server.id, :ip_permissions => ip_permissions
|
154
|
+
server.wait_until_ready
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
if database
|
159
|
+
Inform.info "Waiting for database to become ready" do
|
160
|
+
database.wait_until_ready if database
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def configure_servers_for_database password
|
166
|
+
Inform.info "Configuring servers for database" do
|
167
|
+
servers.each do |server|
|
168
|
+
Inform.debug "%{id}", :id => server.id
|
169
|
+
server.configure_for_database(database, password)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def elb
|
175
|
+
@elb ||= Cloud.elb.load_balancers.detect { |elb| elb.id == name } # XXX - need to refactor
|
176
|
+
end
|
177
|
+
|
178
|
+
def has_elb?
|
179
|
+
elb_descriptions.select { |elb|
|
180
|
+
elb['LoadBalancerName'] == name
|
181
|
+
}.length > 0
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def listeners_from_ports ports
|
187
|
+
ports.map do |port|
|
188
|
+
{
|
189
|
+
'Protocol' => 'TCP',
|
190
|
+
'LoadBalancerPort' => port,
|
191
|
+
'InstancePort' => port,
|
192
|
+
}
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def elb_descriptions name = nil
|
197
|
+
Cloud.elb.describe_load_balancers(name).body['DescribeLoadBalancersResult']['LoadBalancerDescriptions']
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class FogModel
|
2
|
+
def initialize fog_object
|
3
|
+
@fog_object = fog_object
|
4
|
+
end
|
5
|
+
|
6
|
+
def id
|
7
|
+
@fog_object.id
|
8
|
+
end
|
9
|
+
|
10
|
+
def wait_until_ready
|
11
|
+
@fog_object.wait_for { ready? }
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_missing method_sym, *args, &block
|
15
|
+
@fog_object.send(method_sym, *args, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def fog_object
|
21
|
+
@fog_object
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class Profile
|
4
|
+
|
5
|
+
attr_reader :profile_name
|
6
|
+
attr_accessor :ami, :size, :security_groups, :keypair, :count
|
7
|
+
attr_accessor :rds_size, :elb_listener_ports
|
8
|
+
|
9
|
+
AWS_RESOURCES = YAML.load(File.read(File.join(File.dirname(__FILE__), '..', 'aws_resources.yaml')))
|
10
|
+
|
11
|
+
def self.read(profile_name)
|
12
|
+
file = File.read(File.join(ENV['HOME'], '.dew', 'profiles', "#{profile_name}.yaml"))
|
13
|
+
yaml = YAML.load(file)
|
14
|
+
profile = new(profile_name)
|
15
|
+
if yaml['instances']
|
16
|
+
profile.ami = yaml['instances']['amis'][Cloud.region]
|
17
|
+
profile.size = yaml['instances']['size']
|
18
|
+
profile.security_groups = yaml['instances']['security-groups'] || ['default']
|
19
|
+
profile.keypair = yaml['instances']['keypair']
|
20
|
+
profile.count = yaml['instances']['count'].to_i
|
21
|
+
end
|
22
|
+
if yaml['elb']
|
23
|
+
profile.elb_listener_ports = yaml['elb']['listener_ports']
|
24
|
+
end
|
25
|
+
if yaml['rds']
|
26
|
+
profile.rds_size = yaml['rds']['size']
|
27
|
+
end
|
28
|
+
profile
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_elb?
|
32
|
+
elb_listener_ports != nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def has_rds?
|
36
|
+
rds_size != nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(profile_name)
|
40
|
+
@profile_name = profile_name
|
41
|
+
# :ami, :size, :security_groups, :keypair, :count
|
42
|
+
# :rds_size, :elb_listener_ports
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
db_instance_str = "%{memory} memory, %{processor} processor, %{platform} platform, %{io_performance} I/O performance"
|
47
|
+
#instance_str = "%{memory} memory, %{processor} processor, %{storage} storage, %{platform} platform, %{io_performance} I/O performance"
|
48
|
+
instance_str = "%{memory} GB memory, %{processor} ECUs processor, %{storage} GB storage, %{platform}-bit platform, %{io_performance} I/O performance"
|
49
|
+
flavor = Cloud.compute.flavors.detect { |f| f.id == size }
|
50
|
+
instance_hash = { :memory => flavor.ram.to_s, :processor => flavor.cores.to_s, :storage => flavor.disk.to_s, :platform => flavor.bits.to_s, :io_performance => '??' }
|
51
|
+
table { |t|
|
52
|
+
t << [ "#{count} instance#{'s' if count > 1}", "#{size.inspect} (#{instance_str % instance_hash })"]
|
53
|
+
t << ['disk image', ami.inspect]
|
54
|
+
t << ['load balancer', "listener ports: #{elb_listener_ports.inspect}"] if has_elb?
|
55
|
+
t << ['database', "#{rds_size.inspect} (#{db_instance_str % AWS_RESOURCES['db_instance_types'][rds_size]})"] if has_rds?
|
56
|
+
t << ['security groups', security_groups.inspect]
|
57
|
+
t << ['keypair', keypair.inspect]
|
58
|
+
}.to_s
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'gofer'
|
2
|
+
|
3
|
+
class Server < FogModel
|
4
|
+
TEN_SECONDS = 10
|
5
|
+
SIXTY_SECONDS = 60
|
6
|
+
TWO_MINUTES = 120
|
7
|
+
RUNNING_SERVER_STATES = %w{pending running}
|
8
|
+
|
9
|
+
def self.create! ami, size, keypair, groups
|
10
|
+
new(Cloud.compute.servers.create(:image_id => ami, :flavor_id => size, :key_name => keypair, :groups => groups))
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.find tag_name, tag_value
|
14
|
+
Cloud.compute.servers.all("tag:#{tag_name}" => tag_value).select{|s| RUNNING_SERVER_STATES.include?(s.state)}.map {|s| new s }
|
15
|
+
end
|
16
|
+
|
17
|
+
def creator
|
18
|
+
@creator ||= fog_object.tags["Creator"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_tag key, val
|
22
|
+
try_for(SIXTY_SECONDS) {
|
23
|
+
Cloud.compute.tags.create(:resource_id => id, :key => key, :value => val)
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def configure_for_database database, password
|
28
|
+
ssh.write(database.db_environment_file(password), '/tmp/envfile')
|
29
|
+
ssh.run('sudo mv /tmp/envfile /etc/environment')
|
30
|
+
end
|
31
|
+
|
32
|
+
def username
|
33
|
+
'ubuntu'
|
34
|
+
end
|
35
|
+
|
36
|
+
def ssh
|
37
|
+
Gofer::Host.new(public_ip_address, username, :key_data => [File.read(Cloud.keyfile_path(key_name))], :paranoid => false, :quiet => true)
|
38
|
+
end
|
39
|
+
|
40
|
+
def wait_until_ready ssh_timeout=TWO_MINUTES
|
41
|
+
super()
|
42
|
+
Inform.debug("%{id} online at %{ip}, waiting for SSH connection...", :id => id, :ip => public_ip_address)
|
43
|
+
wait_for_ssh ssh_timeout
|
44
|
+
Inform.debug("Connected to %{id} via SSH successfully", :id => id)
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_ami ami_name
|
48
|
+
image_id = Cloud.compute.create_image(id, ami_name, "Created by #{ENV['USER']} on #{Time.now.strftime("%Y-%m-%d")}").body['imageId']
|
49
|
+
|
50
|
+
Inform.debug("Created image at %{id}, waiting for AWS to recognize it...", :id => image_id)
|
51
|
+
# Sometimes takes a while for AWS to realise there's a new image...
|
52
|
+
image = Timeout::timeout(SIXTY_SECONDS) do
|
53
|
+
image = nil
|
54
|
+
while image == nil
|
55
|
+
image = Cloud.compute.images.get(image_id)
|
56
|
+
end
|
57
|
+
image
|
58
|
+
end
|
59
|
+
Inform.debug("Image recognized, waiting for it to become available...")
|
60
|
+
|
61
|
+
image.wait_for { state == 'available' }
|
62
|
+
Inform.debug("Image available, sharing with other accounts...")
|
63
|
+
|
64
|
+
Account.user_ids.each do |user_id|
|
65
|
+
Inform.debug("Sharing %{id} with %{user_id}", :id => image_id, :user_id => user_id)
|
66
|
+
Cloud.compute.modify_image_attributes(image_id, 'launchPermission', 'add', 'UserId' => user_id)
|
67
|
+
end
|
68
|
+
image_id
|
69
|
+
end
|
70
|
+
|
71
|
+
def credentials
|
72
|
+
@credentials ||= if key_name
|
73
|
+
keyfile_path = Cloud.keyfile_path(key_name)
|
74
|
+
|
75
|
+
sanitize_key_file(key_name, keyfile_path)
|
76
|
+
|
77
|
+
"-i #{keyfile_path} -o StrictHostKeyChecking=no #{username}@#{public_ip_address}"
|
78
|
+
else
|
79
|
+
Inform.warning("Server %{id} has no key and therefore can not be accessed.", :id => id)
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def sanitize_key_file name, path
|
87
|
+
begin
|
88
|
+
stat = File.stat(path)
|
89
|
+
raise "Keyfile at #{keyfile_path} not owned by #{ENV['USER']}, can't SSH" if stat.uid != Process.euid
|
90
|
+
if (stat.mode & 077) != 0
|
91
|
+
Inform.info("Changing permissions on key at %{path} to 0600", :path => path) do
|
92
|
+
File.chmod(0600, path)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
rescue Errno::ENOENT
|
96
|
+
raise "Can't find keyfile for #{name} under this account/region (looking in #{path})"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def wait_for_ssh timeout
|
101
|
+
try_for(timeout) {
|
102
|
+
begin
|
103
|
+
Timeout::timeout(15) do
|
104
|
+
ssh.run('uptime')
|
105
|
+
end
|
106
|
+
# SshTimeout magic is here due to a weird bug with Ruby 1.8.7 where the Timeout::Error
|
107
|
+
# will not be caught by wait_for_proc!
|
108
|
+
rescue Timeout::Error => e
|
109
|
+
raise "SSH Timeout Encountered: #{e.to_s}"
|
110
|
+
end
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def try_for(timeout, &block)
|
115
|
+
start_time = Time.now
|
116
|
+
time_spent = 0
|
117
|
+
success = false
|
118
|
+
last_exception = nil
|
119
|
+
while !success && time_spent < timeout
|
120
|
+
begin
|
121
|
+
block.call
|
122
|
+
success = true
|
123
|
+
rescue => e
|
124
|
+
time_spent = Time.now - start_time
|
125
|
+
Inform.debug("Exception: %{m} (%{c}), in block %{block} after %{time_spent}s (retrying until %{timeout}s)...", :c => e.class.to_s, :m => e.message, :block => block, :time_spent => time_spent.to_i, :timeout => timeout)
|
126
|
+
sleep 1
|
127
|
+
last_exception = e
|
128
|
+
end
|
129
|
+
end
|
130
|
+
#raise unless success
|
131
|
+
raise last_exception unless success
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|