cf_deployer 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog.md +6 -0
- data/Rakefile +6 -1
- data/cf_deployer.gemspec +2 -0
- data/fixtures/vcr_cassettes/aws_call_count/driver/auto_scaling_group/healthy_instance_count.yml +243 -0
- data/lib/cf_deployer/deployment_strategy/auto_scaling_group_swap.rb +20 -7
- data/lib/cf_deployer/driver/auto_scaling_group.rb +42 -25
- data/lib/cf_deployer/logger.rb +4 -0
- data/lib/cf_deployer/version.rb +1 -1
- data/spec/aws_call_count/driver/auto_scaling_group_spec.rb +19 -0
- data/spec/aws_call_count_spec_helper.rb +38 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb +308 -31
- data/spec/unit/driver/auto_scaling_group_spec.rb +154 -38
- metadata +35 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b15fbdcf8f040017be2e4f7fd2fca49e99327164
|
4
|
+
data.tar.gz: 6138254475f90d7983da1823754ace6134be9d85
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 209ff6fd2211ae9b37ef0592aaec30066139ee37742cc36bdb45593ad0bc8f4476393ccc76be3d04204b9021bef07621b703bdd09b65b85580f18827ff5ceb9a
|
7
|
+
data.tar.gz: 9d3a7676274ac56d77ef0d545f1dc2441bf9a84ce07bf50cae1aec809df349ff8a5f23202bfdabedf8064d08383ac191d879606c9a80b3586e91792f2819f91b
|
data/ChangeLog.md
CHANGED
@@ -57,3 +57,9 @@ version 1.4.0
|
|
57
57
|
|
58
58
|
version 1.5.0
|
59
59
|
- Treat deployments that end in a rollback as a failure
|
60
|
+
|
61
|
+
version 1.6.0
|
62
|
+
- Improve warm up for AutoScalingGroup based deployments (See: https://github.com/manheim/cf_deployer/issues/32)
|
63
|
+
- Automatically rollback on failures for AutoScalingGroup based deployments (See: https://github.com/manheim/cf_deployer/issues/39)
|
64
|
+
- Improve error message when no stack is exists on ASG-based switch (See: https://github.com/manheim/cf_deployer/issues/50)
|
65
|
+
- Reduce AWS calls during healthy_instance_count (See: https://github.com/manheim/cf_deployer/issues/48)
|
data/Rakefile
CHANGED
@@ -22,7 +22,12 @@ namespace :spec do
|
|
22
22
|
t.pattern = 'spec/functional/**/*_spec.rb'
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
RSpec::Core::RakeTask.new(:aws_call_count) do |t|
|
26
|
+
t.rspec_opts = RSPEC_OPTS
|
27
|
+
t.pattern = 'spec/aws_call_count/**/*_spec.rb'
|
28
|
+
end
|
29
|
+
|
30
|
+
task :all => [:unit, :functional, :aws_call_count]
|
26
31
|
end
|
27
32
|
|
28
33
|
task :default => 'spec:all'
|
data/cf_deployer.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.add_development_dependency 'pry', '~> 0.10.1'
|
19
19
|
gem.add_development_dependency 'rspec', '2.14.1'
|
20
20
|
gem.add_development_dependency 'rake', '~> 10.3.0'
|
21
|
+
gem.add_development_dependency 'webmock', '~> 2.1.0'
|
22
|
+
gem.add_development_dependency 'vcr', '~> 2.9.3'
|
21
23
|
|
22
24
|
gem.files = `git ls-files`.split($\).reject {|f| f =~ /^samples\// }
|
23
25
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
data/fixtures/vcr_cassettes/aws_call_count/driver/auto_scaling_group/healthy_instance_count.yml
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: https://autoscaling.us-east-1.amazonaws.com/
|
6
|
+
response:
|
7
|
+
status:
|
8
|
+
code: 200
|
9
|
+
message: OK
|
10
|
+
body:
|
11
|
+
encoding: US-ASCII
|
12
|
+
string: ! "<DescribeAutoScalingGroupsResponse xmlns=\"http://autoscaling.amazonaws.com/doc/2011-01-01/\">\n
|
13
|
+
\ <DescribeAutoScalingGroupsResult>\n <AutoScalingGroups>\n <member>\n
|
14
|
+
\ <HealthCheckType>ELB</HealthCheckType>\n <LoadBalancerNames>\n
|
15
|
+
\ <member>myELB</member>\n </LoadBalancerNames>\n
|
16
|
+
\ <Instances>\n <member>\n <LaunchConfigurationName>myLaunchConfig</LaunchConfigurationName>\n
|
17
|
+
\ <LifecycleState>InService</LifecycleState>\n <InstanceId>instance1</InstanceId>\n
|
18
|
+
\ <HealthStatus>Healthy</HealthStatus>\n <ProtectedFromScaleIn>false</ProtectedFromScaleIn>\n
|
19
|
+
\ <AvailabilityZone>us-east-1d</AvailabilityZone>\n </member>\n
|
20
|
+
\ <member>\n <LaunchConfigurationName>myLaunchConfig</LaunchConfigurationName>\n
|
21
|
+
\ <LifecycleState>InService</LifecycleState>\n <InstanceId>instance2</InstanceId>\n
|
22
|
+
\ <HealthStatus>Healthy</HealthStatus>\n <ProtectedFromScaleIn>false</ProtectedFromScaleIn>\n
|
23
|
+
\ <AvailabilityZone>us-east-1c</AvailabilityZone>\n </member>\n
|
24
|
+
\ <member>\n <LaunchConfigurationName>myLaunchConfig</LaunchConfigurationName>\n
|
25
|
+
\ <LifecycleState>InService</LifecycleState>\n <InstanceId>instance3</InstanceId>\n
|
26
|
+
\ <HealthStatus>Healthy</HealthStatus>\n <ProtectedFromScaleIn>false</ProtectedFromScaleIn>\n
|
27
|
+
\ <AvailabilityZone>us-east-1e</AvailabilityZone>\n </member>\n
|
28
|
+
\ <member>\n <LaunchConfigurationName>myLaunchConfig</LaunchConfigurationName>\n
|
29
|
+
\ <LifecycleState>InService</LifecycleState>\n <InstanceId>instance4</InstanceId>\n
|
30
|
+
\ <HealthStatus>Healthy</HealthStatus>\n <ProtectedFromScaleIn>false</ProtectedFromScaleIn>\n
|
31
|
+
\ <AvailabilityZone>us-east-1e</AvailabilityZone>\n </member>\n
|
32
|
+
\ </Instances>\n <TerminationPolicies>\n <member>Default</member>\n
|
33
|
+
\ </TerminationPolicies>\n <DefaultCooldown>300</DefaultCooldown>\n
|
34
|
+
\ <AutoScalingGroupARN>arn:aws:autoscaling:us-east-1:12345:autoScalingGroup:12345:autoScalingGroupName/myASG</AutoScalingGroupARN>\n
|
35
|
+
\ <EnabledMetrics/>\n <MaxSize>5</MaxSize>\n <AvailabilityZones>\n
|
36
|
+
\ <member>us-east-1c</member>\n <member>us-east-1d</member>\n
|
37
|
+
\ <member>us-east-1e</member>\n </AvailabilityZones>\n <TargetGroupARNs/>\n
|
38
|
+
\ <Tags/>\n
|
39
|
+
\ <LaunchConfigurationName>myLaunchConfig</LaunchConfigurationName>\n
|
40
|
+
\ <AutoScalingGroupName>myASG</AutoScalingGroupName>\n
|
41
|
+
\ <HealthCheckGracePeriod>600</HealthCheckGracePeriod>\n <NewInstancesProtectedFromScaleIn>false</NewInstancesProtectedFromScaleIn>\n
|
42
|
+
\ <CreatedTime>2016-09-23T03:52:21.733Z</CreatedTime>\n <MinSize>1</MinSize>\n
|
43
|
+
\ <SuspendedProcesses/>\n <DesiredCapacity>4</DesiredCapacity>\n
|
44
|
+
\ <VPCZoneIdentifier>subnet</VPCZoneIdentifier>\n
|
45
|
+
\ </member>\n </AutoScalingGroups>\n </DescribeAutoScalingGroupsResult>\n
|
46
|
+
\ <ResponseMetadata>\n <RequestId>12345</RequestId>\n
|
47
|
+
\ </ResponseMetadata>\n</DescribeAutoScalingGroupsResponse>\n"
|
48
|
+
http_version:
|
49
|
+
recorded_at: Tue, 27 Sep 2016 05:47:40 GMT
|
50
|
+
- request:
|
51
|
+
method: post
|
52
|
+
uri: https://autoscaling.us-east-1.amazonaws.com/
|
53
|
+
response:
|
54
|
+
status:
|
55
|
+
code: 200
|
56
|
+
message: OK
|
57
|
+
body:
|
58
|
+
encoding: US-ASCII
|
59
|
+
string: ! "<DescribeAutoScalingInstancesResponse xmlns=\"http://autoscaling.amazonaws.com/doc/2011-01-01/\">\n
|
60
|
+
\ <DescribeAutoScalingInstancesResult>\n <AutoScalingInstances>\n <member>\n
|
61
|
+
\ <LaunchConfigurationName>myLaunchConfig</LaunchConfigurationName>\n
|
62
|
+
\ <LifecycleState>InService</LifecycleState>\n <AutoScalingGroupName></AutoScalingGroupName>\n
|
63
|
+
\ <InstanceId>instance1</InstanceId>\n <HealthStatus>HEALTHY</HealthStatus>\n
|
64
|
+
\ <ProtectedFromScaleIn>false</ProtectedFromScaleIn>\n <AvailabilityZone>us-east-1d</AvailabilityZone>\n
|
65
|
+
\ </member>\n </AutoScalingInstances>\n </DescribeAutoScalingInstancesResult>\n
|
66
|
+
\ <ResponseMetadata>\n <RequestId>12346</RequestId>\n
|
67
|
+
\ </ResponseMetadata>\n</DescribeAutoScalingInstancesResponse>\n"
|
68
|
+
http_version:
|
69
|
+
recorded_at: Tue, 27 Sep 2016 05:47:40 GMT
|
70
|
+
- request:
|
71
|
+
method: post
|
72
|
+
uri: https://elasticloadbalancing.us-east-1.amazonaws.com/
|
73
|
+
body:
|
74
|
+
encoding: UTF-8
|
75
|
+
string: Action=DescribeInstanceHealth&LoadBalancerName=myELB&Timestamp=2016-09-27T05%3A47%3A41Z&Version=2012-06-01
|
76
|
+
response:
|
77
|
+
status:
|
78
|
+
code: 200
|
79
|
+
message: OK
|
80
|
+
body:
|
81
|
+
encoding: US-ASCII
|
82
|
+
string: ! "<DescribeInstanceHealthResponse xmlns=\"http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/\">\n
|
83
|
+
\ <DescribeInstanceHealthResult>\n <InstanceStates>\n <member>\n <Description>N/A</Description>\n
|
84
|
+
\ <InstanceId>instance1</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
85
|
+
\ <State>InService</State>\n </member>\n <member>\n <Description>N/A</Description>\n
|
86
|
+
\ <InstanceId>instance2</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
87
|
+
\ <State>InService</State>\n </member>\n <member>\n <Description>N/A</Description>\n
|
88
|
+
\ <InstanceId>instance3</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
89
|
+
\ <State>InService</State>\n </member>\n <member>\n <Description>N/A</Description>\n
|
90
|
+
\ <InstanceId>instance4</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
91
|
+
\ <State>InService</State>\n </member>\n </InstanceStates>\n
|
92
|
+
\ </DescribeInstanceHealthResult>\n <ResponseMetadata>\n <RequestId>12347</RequestId>\n
|
93
|
+
\ </ResponseMetadata>\n</DescribeInstanceHealthResponse>\n"
|
94
|
+
http_version:
|
95
|
+
recorded_at: Tue, 27 Sep 2016 05:47:41 GMT
|
96
|
+
- request:
|
97
|
+
method: post
|
98
|
+
uri: https://autoscaling.us-east-1.amazonaws.com/
|
99
|
+
body:
|
100
|
+
encoding: UTF-8
|
101
|
+
string: Action=DescribeAutoScalingInstances&InstanceIds.member.1=instance2&Timestamp=2016-09-27T05%3A47%3A41Z&Version=2011-01-01
|
102
|
+
response:
|
103
|
+
status:
|
104
|
+
code: 200
|
105
|
+
message: OK
|
106
|
+
body:
|
107
|
+
encoding: US-ASCII
|
108
|
+
string: ! "<DescribeAutoScalingInstancesResponse xmlns=\"http://autoscaling.amazonaws.com/doc/2011-01-01/\">\n
|
109
|
+
\ <DescribeAutoScalingInstancesResult>\n <AutoScalingInstances>\n <member>\n
|
110
|
+
\ <LaunchConfigurationName>myLaunchConfig</LaunchConfigurationName>\n
|
111
|
+
\ <LifecycleState>InService</LifecycleState>\n <AutoScalingGroupName>myASG</AutoScalingGroupName>\n
|
112
|
+
\ <InstanceId>instance2</InstanceId>\n <HealthStatus>HEALTHY</HealthStatus>\n
|
113
|
+
\ <ProtectedFromScaleIn>false</ProtectedFromScaleIn>\n <AvailabilityZone>us-east-1c</AvailabilityZone>\n
|
114
|
+
\ </member>\n </AutoScalingInstances>\n </DescribeAutoScalingInstancesResult>\n
|
115
|
+
\ <ResponseMetadata>\n <RequestId>12348</RequestId>\n
|
116
|
+
\ </ResponseMetadata>\n</DescribeAutoScalingInstancesResponse>\n"
|
117
|
+
http_version:
|
118
|
+
recorded_at: Tue, 27 Sep 2016 05:47:42 GMT
|
119
|
+
- request:
|
120
|
+
method: post
|
121
|
+
uri: https://elasticloadbalancing.us-east-1.amazonaws.com/
|
122
|
+
body:
|
123
|
+
encoding: UTF-8
|
124
|
+
string: Action=DescribeInstanceHealth&LoadBalancerName=myELB&Timestamp=2016-09-27T05%3A47%3A42Z&Version=2012-06-01
|
125
|
+
response:
|
126
|
+
status:
|
127
|
+
code: 200
|
128
|
+
message: OK
|
129
|
+
body:
|
130
|
+
encoding: US-ASCII
|
131
|
+
string: ! "<DescribeInstanceHealthResponse xmlns=\"http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/\">\n
|
132
|
+
\ <DescribeInstanceHealthResult>\n <InstanceStates>\n <member>\n <Description>N/A</Description>\n
|
133
|
+
\ <InstanceId>instance1</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
134
|
+
\ <State>InService</State>\n </member>\n <member>\n <Description>N/A</Description>\n
|
135
|
+
\ <InstanceId>instance2</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
136
|
+
\ <State>InService</State>\n </member>\n <member>\n <Description>N/A</Description>\n
|
137
|
+
\ <InstanceId>instance3</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
138
|
+
\ <State>InService</State>\n </member>\n <member>\n <Description>N/A</Description>\n
|
139
|
+
\ <InstanceId>instance4</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
140
|
+
\ <State>InService</State>\n </member>\n </InstanceStates>\n
|
141
|
+
\ </DescribeInstanceHealthResult>\n <ResponseMetadata>\n <RequestId>12349</RequestId>\n
|
142
|
+
\ </ResponseMetadata>\n</DescribeInstanceHealthResponse>\n"
|
143
|
+
http_version:
|
144
|
+
recorded_at: Tue, 27 Sep 2016 05:47:42 GMT
|
145
|
+
- request:
|
146
|
+
method: post
|
147
|
+
uri: https://autoscaling.us-east-1.amazonaws.com/
|
148
|
+
body:
|
149
|
+
encoding: UTF-8
|
150
|
+
string: Action=DescribeAutoScalingInstances&InstanceIds.member.1=instance3&Timestamp=2016-09-27T05%3A47%3A42Z&Version=2011-01-01
|
151
|
+
response:
|
152
|
+
status:
|
153
|
+
code: 200
|
154
|
+
message: OK
|
155
|
+
body:
|
156
|
+
encoding: US-ASCII
|
157
|
+
string: ! "<DescribeAutoScalingInstancesResponse xmlns=\"http://autoscaling.amazonaws.com/doc/2011-01-01/\">\n
|
158
|
+
\ <DescribeAutoScalingInstancesResult>\n <AutoScalingInstances>\n <member>\n
|
159
|
+
\ <LaunchConfigurationName>myLaunchConfig</LaunchConfigurationName>\n
|
160
|
+
\ <LifecycleState>InService</LifecycleState>\n <AutoScalingGroupName>myASG</AutoScalingGroupName>\n
|
161
|
+
\ <InstanceId>instance3</InstanceId>\n <HealthStatus>HEALTHY</HealthStatus>\n
|
162
|
+
\ <ProtectedFromScaleIn>false</ProtectedFromScaleIn>\n <AvailabilityZone>us-east-1e</AvailabilityZone>\n
|
163
|
+
\ </member>\n </AutoScalingInstances>\n </DescribeAutoScalingInstancesResult>\n
|
164
|
+
\ <ResponseMetadata>\n <RequestId>12350</RequestId>\n
|
165
|
+
\ </ResponseMetadata>\n</DescribeAutoScalingInstancesResponse>\n"
|
166
|
+
http_version:
|
167
|
+
recorded_at: Tue, 27 Sep 2016 05:47:42 GMT
|
168
|
+
- request:
|
169
|
+
method: post
|
170
|
+
uri: https://elasticloadbalancing.us-east-1.amazonaws.com/
|
171
|
+
body:
|
172
|
+
encoding: UTF-8
|
173
|
+
string: Action=DescribeInstanceHealth&LoadBalancerName=myELB&Timestamp=2016-09-27T05%3A47%3A42Z&Version=2012-06-01
|
174
|
+
response:
|
175
|
+
status:
|
176
|
+
code: 200
|
177
|
+
message: OK
|
178
|
+
body:
|
179
|
+
encoding: US-ASCII
|
180
|
+
string: ! "<DescribeInstanceHealthResponse xmlns=\"http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/\">\n
|
181
|
+
\ <DescribeInstanceHealthResult>\n <InstanceStates>\n <member>\n <Description>N/A</Description>\n
|
182
|
+
\ <InstanceId>instance1</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
183
|
+
\ <State>InService</State>\n </member>\n <member>\n <Description>N/A</Description>\n
|
184
|
+
\ <InstanceId>instance2</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
185
|
+
\ <State>InService</State>\n </member>\n <member>\n <Description>N/A</Description>\n
|
186
|
+
\ <InstanceId>instance3</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
187
|
+
\ <State>InService</State>\n </member>\n <member>\n <Description>N/A</Description>\n
|
188
|
+
\ <InstanceId>instance4</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
189
|
+
\ <State>InService</State>\n </member>\n </InstanceStates>\n
|
190
|
+
\ </DescribeInstanceHealthResult>\n <ResponseMetadata>\n <RequestId>12351</RequestId>\n
|
191
|
+
\ </ResponseMetadata>\n</DescribeInstanceHealthResponse>\n"
|
192
|
+
http_version:
|
193
|
+
recorded_at: Tue, 27 Sep 2016 05:47:42 GMT
|
194
|
+
- request:
|
195
|
+
method: post
|
196
|
+
uri: https://autoscaling.us-east-1.amazonaws.com/
|
197
|
+
body:
|
198
|
+
encoding: UTF-8
|
199
|
+
string: Action=DescribeAutoScalingInstances&InstanceIds.member.1=instance4&Timestamp=2016-09-27T05%3A47%3A42Z&Version=2011-01-01
|
200
|
+
response:
|
201
|
+
status:
|
202
|
+
code: 200
|
203
|
+
message: OK
|
204
|
+
body:
|
205
|
+
encoding: US-ASCII
|
206
|
+
string: ! "<DescribeAutoScalingInstancesResponse xmlns=\"http://autoscaling.amazonaws.com/doc/2011-01-01/\">\n
|
207
|
+
\ <DescribeAutoScalingInstancesResult>\n <AutoScalingInstances>\n <member>\n
|
208
|
+
\ <LaunchConfigurationName>myLaunchConfig</LaunchConfigurationName>\n
|
209
|
+
\ <LifecycleState>InService</LifecycleState>\n <AutoScalingGroupName>myASG</AutoScalingGroupName>\n
|
210
|
+
\ <InstanceId>instance4</InstanceId>\n <HealthStatus>HEALTHY</HealthStatus>\n
|
211
|
+
\ <ProtectedFromScaleIn>false</ProtectedFromScaleIn>\n <AvailabilityZone>us-east-1e</AvailabilityZone>\n
|
212
|
+
\ </member>\n </AutoScalingInstances>\n </DescribeAutoScalingInstancesResult>\n
|
213
|
+
\ <ResponseMetadata>\n <RequestId>12352</RequestId>\n
|
214
|
+
\ </ResponseMetadata>\n</DescribeAutoScalingInstancesResponse>\n"
|
215
|
+
http_version:
|
216
|
+
recorded_at: Tue, 27 Sep 2016 05:47:42 GMT
|
217
|
+
- request:
|
218
|
+
method: post
|
219
|
+
uri: https://elasticloadbalancing.us-east-1.amazonaws.com/
|
220
|
+
body:
|
221
|
+
encoding: UTF-8
|
222
|
+
string: Action=DescribeInstanceHealth&LoadBalancerName=myELB&Timestamp=2016-09-27T05%3A47%3A42Z&Version=2012-06-01
|
223
|
+
response:
|
224
|
+
status:
|
225
|
+
code: 200
|
226
|
+
message: OK
|
227
|
+
body:
|
228
|
+
encoding: US-ASCII
|
229
|
+
string: ! "<DescribeInstanceHealthResponse xmlns=\"http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/\">\n
|
230
|
+
\ <DescribeInstanceHealthResult>\n <InstanceStates>\n <member>\n <Description>N/A</Description>\n
|
231
|
+
\ <InstanceId>instance1</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
232
|
+
\ <State>InService</State>\n </member>\n <member>\n <Description>N/A</Description>\n
|
233
|
+
\ <InstanceId>instance2</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
234
|
+
\ <State>InService</State>\n </member>\n <member>\n <Description>N/A</Description>\n
|
235
|
+
\ <InstanceId>instance3</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
236
|
+
\ <State>InService</State>\n </member>\n <member>\n <Description>N/A</Description>\n
|
237
|
+
\ <InstanceId>instance4</InstanceId>\n <ReasonCode>N/A</ReasonCode>\n
|
238
|
+
\ <State>InService</State>\n </member>\n </InstanceStates>\n
|
239
|
+
\ </DescribeInstanceHealthResult>\n <ResponseMetadata>\n <RequestId>12353</RequestId>\n
|
240
|
+
\ </ResponseMetadata>\n</DescribeInstanceHealthResponse>\n"
|
241
|
+
http_version:
|
242
|
+
recorded_at: Tue, 27 Sep 2016 05:47:42 GMT
|
243
|
+
recorded_with: VCR 2.9.3
|
@@ -2,12 +2,25 @@ module CfDeployer
|
|
2
2
|
module DeploymentStrategy
|
3
3
|
class AutoScalingGroupSwap < BlueGreen
|
4
4
|
|
5
|
+
def cool_inactive_on_failure
|
6
|
+
yield
|
7
|
+
rescue => e
|
8
|
+
if both_stacks_active?
|
9
|
+
Log.error "Deployment failed - #{e.message} - and both stacks are active. Cooling down failed stack. Look into the failure, and try your deployment again."
|
10
|
+
cool_down(inactive_stack)
|
11
|
+
end
|
12
|
+
|
13
|
+
raise e
|
14
|
+
end
|
15
|
+
|
5
16
|
def deploy
|
6
17
|
check_blue_green_not_both_active 'Deployment'
|
7
18
|
Log.info "Found active stack #{active_stack.name}" if active_stack
|
8
19
|
delete_stack inactive_stack
|
9
|
-
|
10
|
-
|
20
|
+
cool_inactive_on_failure do
|
21
|
+
create_inactive_stack
|
22
|
+
swap_group
|
23
|
+
end
|
11
24
|
run_hook(:'after-swap')
|
12
25
|
Log.info "Active stack has been set to #{inactive_stack.name}"
|
13
26
|
delete_stack(active_stack) if active_stack && !keep_previous_stack
|
@@ -22,8 +35,8 @@ module CfDeployer
|
|
22
35
|
|
23
36
|
def switch
|
24
37
|
check_blue_green_not_both_active 'Switch'
|
25
|
-
raise ApplicationError.new('
|
26
|
-
swap_group true
|
38
|
+
raise ApplicationError.new('Both stacks must exist to switch.') unless both_stacks_exist?
|
39
|
+
cool_inactive_on_failure { swap_group true }
|
27
40
|
end
|
28
41
|
|
29
42
|
private
|
@@ -35,7 +48,7 @@ module CfDeployer
|
|
35
48
|
|
36
49
|
def swap_group is_switching_to_cooled = false
|
37
50
|
is_switching_to_cooled ? warm_up_cooled_stack : warm_up_inactive_stack
|
38
|
-
|
51
|
+
cool_down(active_stack) if active_stack && (is_switching_to_cooled || keep_previous_stack)
|
39
52
|
end
|
40
53
|
|
41
54
|
def keep_previous_stack
|
@@ -56,8 +69,8 @@ module CfDeployer
|
|
56
69
|
warm_up_stack(inactive_stack, active_stack, true)
|
57
70
|
end
|
58
71
|
|
59
|
-
def
|
60
|
-
get_active_asgs(
|
72
|
+
def cool_down stack
|
73
|
+
get_active_asgs(stack).each do |id|
|
61
74
|
asg_driver(id).cool_down
|
62
75
|
end
|
63
76
|
end
|
@@ -3,7 +3,7 @@ module CfDeployer
|
|
3
3
|
class AutoScalingGroup
|
4
4
|
extend Forwardable
|
5
5
|
|
6
|
-
def_delegators :aws_group, :auto_scaling_instances, :ec2_instances, :load_balancers
|
6
|
+
def_delegators :aws_group, :auto_scaling_instances, :ec2_instances, :load_balancers, :desired_capacity
|
7
7
|
|
8
8
|
attr_reader :group_name, :group
|
9
9
|
|
@@ -23,7 +23,7 @@ module CfDeployer
|
|
23
23
|
|
24
24
|
CfDeployer::Driver::DryRun.guard "Skipping ASG warmup" do
|
25
25
|
aws_group.set_desired_capacity desired
|
26
|
-
|
26
|
+
wait_for_desired_capacity
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -50,37 +50,54 @@ module CfDeployer
|
|
50
50
|
instance_info
|
51
51
|
end
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
count = auto_scaling_instances.count do |instance|
|
58
|
-
instance.health_status == 'HEALTHY' && (load_balancers.empty? || instance_in_service?( instance.ec2_instance ))
|
53
|
+
def wait_for_desired_capacity
|
54
|
+
Timeout::timeout(@timeout){
|
55
|
+
until desired_capacity_reached?
|
56
|
+
sleep 15
|
59
57
|
end
|
60
|
-
|
61
|
-
count
|
62
|
-
rescue => e
|
63
|
-
Log.info "Unable to determine healthy instance count due to error: #{e.message}"
|
64
|
-
-1
|
65
|
-
end
|
58
|
+
}
|
66
59
|
end
|
67
60
|
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
61
|
+
def desired_capacity_reached?
|
62
|
+
healthy_instance_count >= desired_capacity
|
63
|
+
end
|
64
|
+
|
65
|
+
def healthy_instance_ids
|
66
|
+
instances = auto_scaling_instances.select do |instance|
|
67
|
+
'HEALTHY'.casecmp(instance.health_status) == 0
|
73
68
|
end
|
69
|
+
instances.map(&:id)
|
74
70
|
end
|
75
71
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
72
|
+
def in_service_instance_ids
|
73
|
+
elbs = load_balancers
|
74
|
+
return [] if elbs.empty?
|
75
|
+
|
76
|
+
ids = elbs.collect(&:instances)
|
77
|
+
.collect(&:health)
|
78
|
+
.to_a
|
79
|
+
.collect { |elb_healths|
|
80
|
+
elb_healths.select { |health| health[:state] == 'InService' }
|
81
|
+
.map { |health| health[:instance].id }
|
82
|
+
}
|
83
|
+
|
84
|
+
ids.inject(:&)
|
82
85
|
end
|
83
86
|
|
87
|
+
def healthy_instance_count
|
88
|
+
AWS.memoize do
|
89
|
+
instances = healthy_instance_ids
|
90
|
+
instances &= in_service_instance_ids unless load_balancers.empty?
|
91
|
+
Log.info "Healthy instance count: #{instances.count}"
|
92
|
+
instances.count
|
93
|
+
end
|
94
|
+
rescue => e
|
95
|
+
Log.error "Unable to determine healthy instance count due to error: #{e.message}"
|
96
|
+
-1
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
84
101
|
def aws_group
|
85
102
|
@my_group ||= AWS::AutoScaling.new.groups[group_name]
|
86
103
|
end
|
data/lib/cf_deployer/logger.rb
CHANGED
data/lib/cf_deployer/version.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'aws_call_count_spec_helper'
|
2
|
+
|
3
|
+
describe CfDeployer::Driver::AutoScalingGroup do
|
4
|
+
it 'makes the minimum number of calls to AWS when there are 4 instances in the ASG' do
|
5
|
+
asg = 'myASG'
|
6
|
+
|
7
|
+
override_aws_environment(AWS_REGION: 'us-east-1') do
|
8
|
+
logs = nil
|
9
|
+
allow(CfDeployer::Log).to receive(:error) { |message| logs = message }
|
10
|
+
driver = CfDeployer::Driver::AutoScalingGroup.new asg
|
11
|
+
VCR.use_cassette("aws_call_count/driver/auto_scaling_group/healthy_instance_count") do
|
12
|
+
expect(driver.send(:healthy_instance_count)).to equal(4), "Logs: #{logs}"
|
13
|
+
end
|
14
|
+
|
15
|
+
expect(WebMock).to have_requested(:post, "https://autoscaling.us-east-1.amazonaws.com/").times(1)
|
16
|
+
expect(WebMock).to have_requested(:post, "https://elasticloadbalancing.us-east-1.amazonaws.com/").times(1)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'webmock/rspec'
|
3
|
+
require 'vcr'
|
4
|
+
|
5
|
+
VCR.configure do |config|
|
6
|
+
config.cassette_library_dir = "fixtures/vcr_cassettes"
|
7
|
+
config.hook_into :webmock
|
8
|
+
end
|
9
|
+
|
10
|
+
def override_aws_environment options = {}
|
11
|
+
options[:AWS_REGION] ||= 'us-east-1'
|
12
|
+
options[:AWS_ACCESS_KEY_ID] ||= 'someId'
|
13
|
+
options[:AWS_SECRET_ACCESS_KEY] ||= 'secretKey'
|
14
|
+
|
15
|
+
override_environment_variables(options) { yield }
|
16
|
+
end
|
17
|
+
|
18
|
+
def override_environment_variables options = {}
|
19
|
+
previous_values = override_previous_values(options)
|
20
|
+
|
21
|
+
yield
|
22
|
+
|
23
|
+
restore_values(previous_values)
|
24
|
+
end
|
25
|
+
|
26
|
+
def override_previous_values options = {}
|
27
|
+
previous_values = options.inject([]) do |memo, (key,value)|
|
28
|
+
memo << { key: key.to_s, value: value, existed: ENV.has_key?(key.to_s), old_value: ENV[key.to_s] }
|
29
|
+
ENV[key.to_s] = value
|
30
|
+
memo
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def restore_values previous_values = []
|
35
|
+
previous_values.each do |value|
|
36
|
+
value[:existed] ? ENV[value[:key]] = value[:old_value] : ENV.delete(value[:key])
|
37
|
+
end
|
38
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,7 +3,20 @@ Dir.glob("#{File.dirname File.absolute_path(__FILE__)}/fakes/*.rb") { |file| req
|
|
3
3
|
|
4
4
|
CfDeployer::Log.log.outputters = nil
|
5
5
|
|
6
|
+
RSPEC_LOG = Logger.new(STDOUT)
|
7
|
+
RSPEC_LOG.level = Logger::WARN
|
8
|
+
|
9
|
+
if ENV['DEBUG']
|
10
|
+
RSPEC_LOG.level = Logger::DEBUG
|
11
|
+
AWS.config :logger => RSPEC_LOG
|
12
|
+
end
|
13
|
+
|
6
14
|
def puts *args
|
7
15
|
|
8
16
|
end
|
9
17
|
|
18
|
+
def ignore_errors
|
19
|
+
yield
|
20
|
+
rescue => e
|
21
|
+
RSPEC_LOG.debug "Intentionally ignoring error: #{e.message}"
|
22
|
+
end
|
@@ -127,6 +127,114 @@ describe 'Auto Scaling Group Swap Deployment Strategy' do
|
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
130
|
+
context 'on deployment failure' do
|
131
|
+
context 'in stack creation' do
|
132
|
+
context 'and only one stack is active' do
|
133
|
+
it 'should not cool down the only available stack' do
|
134
|
+
strategy = create_strategy(blue: :active)
|
135
|
+
error = RuntimeError.new("Error before inactive_stack became active")
|
136
|
+
|
137
|
+
expect(strategy).to receive(:create_inactive_stack).and_raise(error)
|
138
|
+
expect(strategy).to_not receive(:cool_down)
|
139
|
+
|
140
|
+
ignore_errors { strategy.deploy }
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should report the deployment as a failure' do
|
144
|
+
strategy = create_strategy(blue: :active)
|
145
|
+
error = RuntimeError.new("Error before inactive_stack became active")
|
146
|
+
|
147
|
+
expect(strategy).to receive(:create_inactive_stack).and_raise(error)
|
148
|
+
expect { strategy.deploy }.to raise_error(error)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'and both stacks are active' do
|
153
|
+
it 'should cool down inactive stack' do
|
154
|
+
strategy = create_strategy(blue: :active)
|
155
|
+
inactive_stack = strategy.send(:green_stack)
|
156
|
+
|
157
|
+
expect(strategy).to receive(:create_inactive_stack) do
|
158
|
+
activate_stack(inactive_stack)
|
159
|
+
raise RuntimeError.new("Error after inactive_stack became active")
|
160
|
+
end
|
161
|
+
|
162
|
+
expect(strategy).to receive(:cool_down).with(inactive_stack)
|
163
|
+
ignore_errors { strategy.deploy }
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should report the deployment as a failure' do
|
167
|
+
strategy = create_strategy(blue: :active)
|
168
|
+
inactive_stack = strategy.send(:green_stack)
|
169
|
+
error = RuntimeError.new("Error after inactive_stack became active")
|
170
|
+
|
171
|
+
expect(strategy).to receive(:create_inactive_stack) do
|
172
|
+
activate_stack(inactive_stack)
|
173
|
+
raise error
|
174
|
+
end
|
175
|
+
|
176
|
+
allow(strategy).to receive(:cool_down)
|
177
|
+
expect { strategy.deploy }.to raise_error(error)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context 'in asg swap' do
|
183
|
+
# This shouldn't be possible - a stack normally becomes active at creation
|
184
|
+
context 'and only one stack is active' do
|
185
|
+
it 'should not cool down the only available stack' do
|
186
|
+
strategy = create_strategy(blue: :active)
|
187
|
+
error = RuntimeError.new("Error during swap")
|
188
|
+
|
189
|
+
# The stack would normally be active after create_inactive_stack
|
190
|
+
allow(strategy).to receive(:create_inactive_stack)
|
191
|
+
expect(strategy).to receive(:swap_group).and_raise(error)
|
192
|
+
expect(strategy).to_not receive(:cool_down)
|
193
|
+
|
194
|
+
ignore_errors { strategy.deploy }
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'should report the deployment as a failure' do
|
198
|
+
strategy = create_strategy(blue: :active)
|
199
|
+
error = RuntimeError.new("Error during swap")
|
200
|
+
|
201
|
+
# The stack would normally be active after create_inactive_stack
|
202
|
+
allow(strategy).to receive(:create_inactive_stack)
|
203
|
+
expect(strategy).to receive(:swap_group).and_raise(error)
|
204
|
+
expect(strategy).to_not receive(:cool_down)
|
205
|
+
|
206
|
+
expect { strategy.deploy }.to raise_error(error)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'and both stacks are active' do
|
211
|
+
it 'should cool down inactive stack' do
|
212
|
+
strategy = create_strategy(blue: :active)
|
213
|
+
inactive_stack = strategy.send(:green_stack)
|
214
|
+
error = RuntimeError.new("Error during swap")
|
215
|
+
|
216
|
+
allow(strategy).to receive(:create_inactive_stack) { activate_stack(inactive_stack) }
|
217
|
+
expect(strategy).to receive(:swap_group).and_raise(error)
|
218
|
+
expect(strategy).to receive(:cool_down).with(inactive_stack)
|
219
|
+
|
220
|
+
ignore_errors { strategy.deploy }
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'should report the deployment as a failure' do
|
224
|
+
strategy = create_strategy(blue: :active)
|
225
|
+
inactive_stack = strategy.send(:green_stack)
|
226
|
+
error = RuntimeError.new("Error during swap")
|
227
|
+
|
228
|
+
allow(strategy).to receive(:create_inactive_stack) { activate_stack(inactive_stack) }
|
229
|
+
expect(strategy).to receive(:swap_group).and_raise(error)
|
230
|
+
expect(strategy).to receive(:cool_down).with(inactive_stack)
|
231
|
+
|
232
|
+
expect { strategy.deploy }.to raise_error(error)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
130
238
|
context 'has active group' do
|
131
239
|
it 'should deploy blue stack if green stack is active' do
|
132
240
|
blue_stack.live!
|
@@ -306,62 +414,169 @@ describe 'Auto Scaling Group Swap Deployment Strategy' do
|
|
306
414
|
describe '#switch' do
|
307
415
|
context 'both stacks are active' do
|
308
416
|
it 'should raise an error' do
|
309
|
-
|
310
|
-
|
311
|
-
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
312
|
-
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blueASG') { blue_asg_driver }
|
313
|
-
allow(green_asg_driver).to receive(:describe) { {desired: 1, min: 1, max: 3 } }
|
314
|
-
allow(blue_asg_driver).to receive(:describe) { {desired: 1, min: 1, max: 3 } }
|
417
|
+
strategy = create_strategy(blue: :active, green: :active)
|
418
|
+
error = 'Found both auto-scaling-groups, ["greenASG", "blueASG"], in green and blue stacks are active. Switch aborted!'
|
315
419
|
|
316
|
-
|
317
|
-
expect{strategy.switch}.to raise_error 'Found both auto-scaling-groups, ["greenASG", "blueASG"], in green and blue stacks are active. Switch aborted!'
|
420
|
+
expect { strategy.switch }.to raise_error(error)
|
318
421
|
end
|
319
422
|
end
|
320
423
|
|
321
424
|
context 'both stacks do not exist' do
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
425
|
+
let(:error) { 'Both stacks must exist to switch.' }
|
426
|
+
|
427
|
+
context '(only green exists)' do
|
428
|
+
it 'should raise an error' do
|
429
|
+
strategy = create_strategy(green: :active, blue: :dead)
|
430
|
+
|
431
|
+
expect { strategy.switch }.to raise_error(error)
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
context '(only blue exists)' do
|
436
|
+
it 'should raise an error' do
|
437
|
+
strategy = create_strategy(green: :dead, blue: :active)
|
438
|
+
|
439
|
+
expect { strategy.switch }.to raise_error(error)
|
440
|
+
end
|
441
|
+
end
|
328
442
|
|
329
|
-
|
330
|
-
|
443
|
+
context '(no stack exists)' do
|
444
|
+
it 'should raise an error' do
|
445
|
+
strategy = create_strategy(green: :dead, blue: :dead)
|
446
|
+
|
447
|
+
expect { strategy.switch }.to raise_error(error)
|
448
|
+
end
|
331
449
|
end
|
332
450
|
end
|
333
451
|
|
334
452
|
context 'green stack is active' do
|
453
|
+
let(:strategy) { create_strategy(green: :active, blue: :inactive) }
|
454
|
+
|
335
455
|
it 'should warm up blue stack and cool down green stack' do
|
336
|
-
green_stack
|
337
|
-
blue_stack
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
456
|
+
active_stack = strategy.send(:green_stack)
|
457
|
+
inactive_stack = strategy.send(:blue_stack)
|
458
|
+
|
459
|
+
expect(strategy).to receive(:warm_up_stack).with(inactive_stack, active_stack, true)
|
460
|
+
expect(strategy).to receive(:cool_down).with(active_stack)
|
461
|
+
|
462
|
+
strategy.switch
|
463
|
+
end
|
464
|
+
|
465
|
+
context 'swap fails' do
|
466
|
+
context 'before blue stack becomes active' do
|
467
|
+
let(:error) { 'Error before inactive stack becomes active' }
|
468
|
+
|
469
|
+
it 'does not cool down any stack' do
|
470
|
+
expect(strategy).to receive(:warm_up_stack).and_raise(error)
|
471
|
+
expect(strategy).to_not receive(:cool_down)
|
472
|
+
|
473
|
+
ignore_errors { strategy.switch }
|
474
|
+
end
|
343
475
|
|
344
|
-
|
345
|
-
|
476
|
+
it 'reports the switch as a failure' do
|
477
|
+
expect(strategy).to receive(:warm_up_stack).and_raise(error)
|
478
|
+
allow(strategy).to receive(:cool_down)
|
479
|
+
|
480
|
+
expect { strategy.switch }.to raise_error(error)
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
context 'after both stacks became active' do
|
485
|
+
let(:error) { 'Error after inactive stack becomes active ' }
|
486
|
+
|
487
|
+
it 'cools down the blue stack' do
|
488
|
+
expect(strategy).to receive(:warm_up_stack) do
|
489
|
+
expect(strategy).to receive(:both_stacks_active?).and_return(true)
|
490
|
+
raise error
|
491
|
+
end
|
492
|
+
|
493
|
+
inactive_stack = strategy.send(:blue_stack)
|
494
|
+
expect(strategy).to receive(:cool_down).with(inactive_stack)
|
495
|
+
|
496
|
+
ignore_errors { strategy.switch }
|
497
|
+
end
|
498
|
+
|
499
|
+
it 'reports the switch as a failure' do
|
500
|
+
expect(strategy).to receive(:warm_up_stack) do
|
501
|
+
expect(strategy).to receive(:both_stacks_active?).and_return(true)
|
502
|
+
raise error
|
503
|
+
end
|
504
|
+
|
505
|
+
expect { strategy.switch }.to raise_error(error)
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
context 'blue stack is active' do
|
512
|
+
let(:strategy) { create_strategy(green: :inactive, blue: :active) }
|
513
|
+
|
514
|
+
it 'should warm up green stack and cool down blue stack' do
|
515
|
+
active_stack = strategy.send(:blue_stack)
|
516
|
+
inactive_stack = strategy.send(:green_stack)
|
517
|
+
|
518
|
+
expect(strategy).to receive(:warm_up_stack).with(inactive_stack, active_stack, true)
|
519
|
+
expect(strategy).to receive(:cool_down).with(active_stack)
|
346
520
|
|
347
|
-
strategy = CfDeployer::DeploymentStrategy.create(app, env, component, context)
|
348
521
|
strategy.switch
|
349
522
|
end
|
523
|
+
|
524
|
+
context 'swap fails' do
|
525
|
+
context 'before green stack becomes active' do
|
526
|
+
let(:error) { 'Error before inactive stack becomes active' }
|
527
|
+
|
528
|
+
it 'does not cool down any stack' do
|
529
|
+
expect(strategy).to receive(:warm_up_stack).and_raise(error)
|
530
|
+
expect(strategy).to_not receive(:cool_down)
|
531
|
+
|
532
|
+
ignore_errors { strategy.switch }
|
533
|
+
end
|
534
|
+
|
535
|
+
it 'reports the switch as a failure' do
|
536
|
+
expect(strategy).to receive(:warm_up_stack).and_raise(error)
|
537
|
+
allow(strategy).to receive(:cool_down)
|
538
|
+
|
539
|
+
expect { strategy.switch }.to raise_error(error)
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
context 'after both stacks become active' do
|
544
|
+
let(:error) { 'Error after inactive stack becomes active ' }
|
545
|
+
|
546
|
+
it 'cools down the green stack' do
|
547
|
+
expect(strategy).to receive(:warm_up_stack) do
|
548
|
+
expect(strategy).to receive(:both_stacks_active?).and_return(true)
|
549
|
+
raise error
|
550
|
+
end
|
551
|
+
|
552
|
+
inactive_stack = strategy.send(:green_stack)
|
553
|
+
expect(strategy).to receive(:cool_down).with(inactive_stack)
|
554
|
+
|
555
|
+
ignore_errors { strategy.switch }
|
556
|
+
end
|
557
|
+
|
558
|
+
it 'reports the switch as a failure' do
|
559
|
+
expect(strategy).to receive(:warm_up_stack) do
|
560
|
+
expect(strategy).to receive(:both_stacks_active?).and_return(true)
|
561
|
+
raise error
|
562
|
+
end
|
563
|
+
|
564
|
+
expect { strategy.switch }.to raise_error(error)
|
565
|
+
end
|
566
|
+
end
|
567
|
+
end
|
350
568
|
end
|
351
569
|
end
|
352
570
|
|
353
|
-
context '#
|
571
|
+
context '#cool_down' do
|
354
572
|
it 'should cool down only those ASGs which actually exist' do
|
355
573
|
blue_stack.live!
|
356
|
-
green_stack.die!
|
357
|
-
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
358
574
|
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blueASG') { blue_asg_driver }
|
359
|
-
allow(green_asg_driver).to receive(:describe) { {desired: 0, min: 0, max: 0 } }
|
360
575
|
allow(blue_asg_driver).to receive(:describe) { {desired: 1, min: 1, max: 3 } }
|
361
576
|
|
362
577
|
strategy = CfDeployer::DeploymentStrategy.create(app, env, component, context)
|
363
578
|
expect(blue_asg_driver).to receive(:cool_down)
|
364
|
-
strategy.send(:
|
579
|
+
strategy.send(:cool_down, blue_stack)
|
365
580
|
end
|
366
581
|
end
|
367
582
|
|
@@ -488,4 +703,66 @@ describe 'Auto Scaling Group Swap Deployment Strategy' do
|
|
488
703
|
asg_swap.send(:stack_active?, blue_stack).should be(true)
|
489
704
|
end
|
490
705
|
end
|
706
|
+
|
707
|
+
def default_options
|
708
|
+
{
|
709
|
+
app_name: 'app',
|
710
|
+
environment: 'environment',
|
711
|
+
component: 'component',
|
712
|
+
context: {
|
713
|
+
:'deployment-strategy' => 'auto-scaling-group-swap',
|
714
|
+
:settings => {
|
715
|
+
:'auto-scaling-group-name-output' => ['AutoScalingGroupID']
|
716
|
+
}
|
717
|
+
}
|
718
|
+
}
|
719
|
+
end
|
720
|
+
|
721
|
+
def create_strategy original_options = {}
|
722
|
+
options = default_options.merge(original_options)
|
723
|
+
|
724
|
+
create_stack(:blue, options.delete(:blue) || :dead, options)
|
725
|
+
create_stack(:green, options.delete(:green) || :dead, options)
|
726
|
+
|
727
|
+
CfDeployer::DeploymentStrategy.create(options[:app_name], options[:environment], options[:component], options[:context])
|
728
|
+
end
|
729
|
+
|
730
|
+
def create_stack color, status = :active, original_options = {}
|
731
|
+
options = default_options.merge(original_options)
|
732
|
+
|
733
|
+
stack = Fakes::Stack.new(name: color.to_s, outputs: {'web-elb-name' => "#{color}-elb"}, parameters: { name: color.to_s})
|
734
|
+
|
735
|
+
stack_color_name = (color.to_s == 'green' ? 'G' : 'B')
|
736
|
+
stack_name = "#{options[:app_name]}-#{options[:environment]}-#{options[:component]}-#{stack_color_name}"
|
737
|
+
allow(CfDeployer::Stack).to receive(:new).with(stack_name, options[:component], options[:context]).and_return(stack)
|
738
|
+
|
739
|
+
stack.tap do
|
740
|
+
case status
|
741
|
+
when :active; activate_stack(stack)
|
742
|
+
when :inactive; activate_stack(stack, { desired: 0, max: 0, min: 0 })
|
743
|
+
when :dead; kill_stack(stack)
|
744
|
+
else raise "Trying to create stack with unknown status; #{status}"
|
745
|
+
end
|
746
|
+
end
|
747
|
+
end
|
748
|
+
|
749
|
+
def activate_stack stack, instances = {}
|
750
|
+
stack.live!
|
751
|
+
|
752
|
+
allow(stack).to receive(:output).with('AutoScalingGroupID').and_return("#{stack.name}ASG")
|
753
|
+
allow(stack).to receive(:find_output).with('AutoScalingGroupID').and_return("#{stack.name}ASG")
|
754
|
+
allow(stack).to receive(:resource_statuses).and_return(asg_ids("#{stack.name}ASG"))
|
755
|
+
|
756
|
+
asg_driver = double("#{stack.name}_asg_driver")
|
757
|
+
instances[:desired] ||= 2
|
758
|
+
instances[:min] ||= 1
|
759
|
+
instances[:max] ||= 5
|
760
|
+
|
761
|
+
allow(asg_driver).to receive(:describe).and_return(instances)
|
762
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with("#{stack.name}ASG") { asg_driver }
|
763
|
+
end
|
764
|
+
|
765
|
+
def kill_stack stack
|
766
|
+
stack.die!
|
767
|
+
end
|
491
768
|
end
|
@@ -27,79 +27,152 @@ describe 'Autoscaling group driver' do
|
|
27
27
|
|
28
28
|
describe '#warm_up' do
|
29
29
|
it 'should warm up the group to the desired size' do
|
30
|
-
expect(group).to receive(:auto_scaling_instances){[instance1, instance2]}
|
31
30
|
expect(group).to receive(:set_desired_capacity).with(2)
|
31
|
+
expect(@driver).to receive(:wait_for_desired_capacity)
|
32
32
|
@driver.warm_up 2
|
33
33
|
end
|
34
34
|
|
35
35
|
it 'should wait for the warm up of the group even if desired is the same as the minimum' do
|
36
|
-
expect(group).to receive(:auto_scaling_instances){[instance2]}
|
37
36
|
expect(group).to receive(:set_desired_capacity).with(1)
|
37
|
+
expect(@driver).to receive(:wait_for_desired_capacity)
|
38
38
|
@driver.warm_up 1
|
39
39
|
end
|
40
40
|
|
41
41
|
it 'should ignore warming up if desired number is less than min size of the group' do
|
42
42
|
expect(group).not_to receive(:set_desired_capacity)
|
43
|
+
expect(@driver).not_to receive(:wait_for_desired_capacity)
|
43
44
|
@driver.warm_up 0
|
44
45
|
end
|
45
46
|
|
46
47
|
it 'should warm up to maximum if desired number is greater than maximum size of group' do
|
47
|
-
expect(group).to receive(:auto_scaling_instances){[instance1, instance2, instance3, instance4]}
|
48
48
|
expect(group).to receive(:set_desired_capacity).with(4)
|
49
|
+
expect(@driver).to receive(:wait_for_desired_capacity)
|
49
50
|
@driver.warm_up 5
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
|
-
describe '#
|
54
|
-
it '
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
describe '#healthy_instance_ids' do
|
55
|
+
it 'returns the ids of all instances that are healthy' do
|
56
|
+
instance1 = double('instance1', :health_status => 'HEALTHY', id: 'instance1')
|
57
|
+
instance2 = double('instance2', :health_status => 'HEALTHY', id: 'instance2')
|
58
|
+
instance3 = double('instance3', :health_status => 'UNHEALTHY', id: 'instance3')
|
59
|
+
instance4 = double('instance4', :health_status => 'HEALTHY', id: 'instance4')
|
60
|
+
allow(group).to receive(:auto_scaling_instances){[instance1, instance2, instance3, instance4]}
|
61
|
+
|
62
|
+
expect(@driver.healthy_instance_ids).to eql ['instance1', 'instance2', 'instance4']
|
58
63
|
end
|
59
64
|
|
60
|
-
it '
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
expect(@driver.
|
65
|
+
it 'returns the ids of all instances that are healthy (case insensitive)' do
|
66
|
+
instance1 = double('instance1', :health_status => 'HealThy', id: 'instance1')
|
67
|
+
allow(group).to receive(:auto_scaling_instances){[instance1]}
|
68
|
+
|
69
|
+
expect(@driver.healthy_instance_ids).to eql ['instance1']
|
65
70
|
end
|
71
|
+
end
|
66
72
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
allow(group).to receive(:load_balancers) { [load_balancer] }
|
72
|
-
allow(group).to receive(:auto_scaling_instances) { [instance1, instance2] }
|
73
|
+
describe '#in_service_instance_ids' do
|
74
|
+
context 'when there are no load balancers' do
|
75
|
+
it 'returns no ids' do
|
76
|
+
allow(group).to receive(:load_balancers).and_return([])
|
73
77
|
|
74
|
-
expect(@driver.
|
78
|
+
expect(@driver.in_service_instance_ids).to eq []
|
75
79
|
end
|
80
|
+
end
|
76
81
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
allow(group).to receive(:load_balancers) { [load_balancer] }
|
82
|
+
context 'when there is only 1 elb' do
|
83
|
+
it 'returns the ids of all instances that are in service' do
|
84
|
+
health1 = { state: 'InService', instance: double('i1', id: 'instance1') }
|
85
|
+
health2 = { state: 'OutOfService', instance: double('i2', id: 'instance2') }
|
86
|
+
health3 = { state: 'InService', instance: double('i3', id: 'instance3') }
|
87
|
+
health4 = { state: 'InService', instance: double('i4', id: 'instance4') }
|
84
88
|
|
85
|
-
|
89
|
+
instance_collection = double('instance_collection', health: [health1, health2, health3, health4])
|
90
|
+
elb = double('elb', instances: instance_collection)
|
91
|
+
allow(group).to receive(:load_balancers).and_return([ elb ])
|
92
|
+
|
93
|
+
expect(@driver.in_service_instance_ids).to eql ['instance1', 'instance3', 'instance4']
|
86
94
|
end
|
87
95
|
end
|
88
96
|
|
89
|
-
context 'when there are multiple elbs
|
90
|
-
it '
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
+
context 'when there are multiple elbs' do
|
98
|
+
it 'returns only the ids of instances that are in all ELBs' do
|
99
|
+
health1 = { state: 'InService', instance: double('i1', id: 'instance1') }
|
100
|
+
health2 = { state: 'InService', instance: double('i2', id: 'instance2') }
|
101
|
+
health3 = { state: 'InService', instance: double('i3', id: 'instance3') }
|
102
|
+
health4 = { state: 'InService', instance: double('i4', id: 'instance4') }
|
103
|
+
health5 = { state: 'InService', instance: double('i5', id: 'instance5') }
|
104
|
+
|
105
|
+
instance_collection1 = double('instance_collection1', health: [health1, health2, health3])
|
106
|
+
instance_collection2 = double('instance_collection2', health: [health2, health3, health4])
|
107
|
+
instance_collection3 = double('instance_collection3', health: [health2, health3, health5])
|
108
|
+
elb1 = double('elb1', instances: instance_collection1)
|
109
|
+
elb2 = double('elb2', instances: instance_collection2)
|
110
|
+
elb3 = double('elb3', instances: instance_collection3)
|
111
|
+
allow(group).to receive(:load_balancers).and_return([ elb1, elb2, elb3 ])
|
112
|
+
|
113
|
+
# Only instance 2 and 3 are associated with all ELB's
|
114
|
+
expect(@driver.in_service_instance_ids).to eql ['instance2', 'instance3']
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'returns only the ids instances that are InService in all ELBs' do
|
118
|
+
health11 = { state: 'OutOfService', instance: double('i1', id: 'instance1') }
|
119
|
+
health12 = { state: 'InService', instance: double('i2', id: 'instance2') }
|
120
|
+
health13 = { state: 'InService', instance: double('i3', id: 'instance3') }
|
121
|
+
|
122
|
+
health21 = { state: 'InService', instance: double('i1', id: 'instance1') }
|
123
|
+
health22 = { state: 'InService', instance: double('i2', id: 'instance2') }
|
124
|
+
health23 = { state: 'OutOfService', instance: double('i3', id: 'instance3') }
|
125
|
+
|
126
|
+
health31 = { state: 'InService', instance: double('i1', id: 'instance1') }
|
127
|
+
health32 = { state: 'InService', instance: double('i2', id: 'instance2') }
|
128
|
+
health33 = { state: 'InService', instance: double('i3', id: 'instance3') }
|
129
|
+
|
130
|
+
instance_collection1 = double('instance_collection1', health: [health11, health12, health13])
|
131
|
+
instance_collection2 = double('instance_collection2', health: [health21, health22, health23])
|
132
|
+
instance_collection3 = double('instance_collection3', health: [health31, health32, health33])
|
97
133
|
|
98
|
-
|
134
|
+
elb1 = double('elb1', instances: instance_collection1)
|
135
|
+
elb2 = double('elb2', instances: instance_collection2)
|
136
|
+
elb3 = double('elb3', instances: instance_collection3)
|
137
|
+
|
138
|
+
allow(group).to receive(:load_balancers).and_return([ elb1, elb2, elb3 ])
|
139
|
+
|
140
|
+
# Only instance 2 is InService across all ELB's
|
141
|
+
expect(@driver.in_service_instance_ids).to eql ['instance2']
|
99
142
|
end
|
100
143
|
end
|
101
144
|
end
|
102
145
|
|
146
|
+
describe '#healthy_instance_count' do
|
147
|
+
context 'when there are no load balancers' do
|
148
|
+
it 'should return the number of healthy instances' do
|
149
|
+
healthy_instance_ids = ['1', '3', '4', '5']
|
150
|
+
allow(@driver).to receive(:load_balancers).and_return([])
|
151
|
+
expect(@driver).to receive(:healthy_instance_ids).and_return(healthy_instance_ids)
|
152
|
+
|
153
|
+
expect(@driver.healthy_instance_count).to eql(healthy_instance_ids.count)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'when load balancers exist' do
|
158
|
+
it 'should return the number of instances that are both healthy, and in service' do
|
159
|
+
healthy_instance_ids = ['1', '3', '4', '5']
|
160
|
+
in_service_instance_ids = ['3', '4']
|
161
|
+
allow(@driver).to receive(:load_balancers).and_return(double('elb', empty?: false))
|
162
|
+
expect(@driver).to receive(:healthy_instance_ids).and_return(healthy_instance_ids)
|
163
|
+
expect(@driver).to receive(:in_service_instance_ids).and_return(in_service_instance_ids)
|
164
|
+
|
165
|
+
# Only instances 3 and 4 are both healthy and in service
|
166
|
+
expect(@driver.healthy_instance_count).to eql(2)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'health check should be resilient against intermittent errors' do
|
171
|
+
expect(@driver).to receive(:healthy_instance_ids).and_raise("Some error")
|
172
|
+
expect(@driver.healthy_instance_count).to eql -1
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
103
176
|
describe '#cool_down' do
|
104
177
|
it 'should cool down group' do
|
105
178
|
expect(group).to receive(:update).with({min_size: 0, max_size: 0})
|
@@ -113,7 +186,7 @@ describe 'Autoscaling group driver' do
|
|
113
186
|
hash = {:max => 5, :min => 2, :desired => 3}
|
114
187
|
allow(group).to receive(:auto_scaling_instances){[instance1, instance2, instance3]}
|
115
188
|
expect(group).to receive(:update).with({:min_size => 2, :max_size => 5})
|
116
|
-
expect(
|
189
|
+
expect(@driver).to receive(:warm_up).with(3)
|
117
190
|
@driver.warm_up_cooled_group hash
|
118
191
|
end
|
119
192
|
end
|
@@ -131,4 +204,47 @@ describe 'Autoscaling group driver' do
|
|
131
204
|
expect(@driver.instance_statuses).to eq( { 'i-abcd1234' => returned_status } )
|
132
205
|
end
|
133
206
|
end
|
207
|
+
|
208
|
+
describe '#wait_for_desired_capacity' do
|
209
|
+
it 'completes if desired capacity reached' do
|
210
|
+
expect(@driver).to receive(:desired_capacity_reached?).and_return(true)
|
211
|
+
|
212
|
+
@driver.wait_for_desired_capacity
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'times out if desired capacity is not reached' do
|
216
|
+
expect(@driver).to receive(:desired_capacity_reached?).and_return(false)
|
217
|
+
|
218
|
+
expect { @driver.wait_for_desired_capacity }.to raise_error(Timeout::Error)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe '#desired_capacity_reached?' do
|
223
|
+
it 'returns true if healthy instance count matches desired capacity' do
|
224
|
+
expected_number = 5
|
225
|
+
|
226
|
+
expect(group).to receive(:desired_capacity).and_return(expected_number)
|
227
|
+
expect(@driver).to receive(:healthy_instance_count).and_return(expected_number)
|
228
|
+
|
229
|
+
expect(@driver.desired_capacity_reached?).to be_true
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'returns false if healthy instance count is less than desired capacity' do
|
233
|
+
expected_number = 5
|
234
|
+
|
235
|
+
expect(group).to receive(:desired_capacity).and_return(expected_number)
|
236
|
+
expect(@driver).to receive(:healthy_instance_count).and_return(expected_number - 1)
|
237
|
+
|
238
|
+
expect(@driver.desired_capacity_reached?).to be_false
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'returns true if healthy instance count is more than desired capacity' do
|
242
|
+
expected_number = 5
|
243
|
+
|
244
|
+
expect(group).to receive(:desired_capacity).and_return(expected_number)
|
245
|
+
expect(@driver).to receive(:healthy_instance_count).and_return(expected_number + 1)
|
246
|
+
|
247
|
+
expect(@driver.desired_capacity_reached?).to be_true
|
248
|
+
end
|
249
|
+
end
|
134
250
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cf_deployer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jame Brechtel
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2016-
|
14
|
+
date: 2016-10-04 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: aws-sdk
|
@@ -139,6 +139,34 @@ dependencies:
|
|
139
139
|
- - ~>
|
140
140
|
- !ruby/object:Gem::Version
|
141
141
|
version: 10.3.0
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: webmock
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ~>
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: 2.1.0
|
149
|
+
type: :development
|
150
|
+
prerelease: false
|
151
|
+
version_requirements: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ~>
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: 2.1.0
|
156
|
+
- !ruby/object:Gem::Dependency
|
157
|
+
name: vcr
|
158
|
+
requirement: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - ~>
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 2.9.3
|
163
|
+
type: :development
|
164
|
+
prerelease: false
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - ~>
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: 2.9.3
|
142
170
|
description: For automatic blue green deployment flow on CloudFormation.
|
143
171
|
email:
|
144
172
|
- jbrechtel@gmail.com
|
@@ -162,6 +190,7 @@ files:
|
|
162
190
|
- Rakefile
|
163
191
|
- bin/cf_deploy
|
164
192
|
- cf_deployer.gemspec
|
193
|
+
- fixtures/vcr_cassettes/aws_call_count/driver/auto_scaling_group/healthy_instance_count.yml
|
165
194
|
- lib/cf_deployer.rb
|
166
195
|
- lib/cf_deployer/application.rb
|
167
196
|
- lib/cf_deployer/application_error.rb
|
@@ -188,6 +217,8 @@ files:
|
|
188
217
|
- lib/cf_deployer/stack.rb
|
189
218
|
- lib/cf_deployer/status_presenter.rb
|
190
219
|
- lib/cf_deployer/version.rb
|
220
|
+
- spec/aws_call_count/driver/auto_scaling_group_spec.rb
|
221
|
+
- spec/aws_call_count_spec_helper.rb
|
191
222
|
- spec/fakes/instance.rb
|
192
223
|
- spec/fakes/route53_client.rb
|
193
224
|
- spec/fakes/stack.rb
|
@@ -240,6 +271,8 @@ specification_version: 4
|
|
240
271
|
summary: Support multiple components deployment using CloudFormation templates with
|
241
272
|
multiple blue green strategies.
|
242
273
|
test_files:
|
274
|
+
- spec/aws_call_count/driver/auto_scaling_group_spec.rb
|
275
|
+
- spec/aws_call_count_spec_helper.rb
|
243
276
|
- spec/fakes/instance.rb
|
244
277
|
- spec/fakes/route53_client.rb
|
245
278
|
- spec/fakes/stack.rb
|