@@ -162,6 +162,104 @@ module VCAP::CloudController
162162 describe '#canary' do
163163 let ( :state ) { DeploymentModel ::PREPAUSED_STATE }
164164 let ( :current_deploying_instances ) { 1 }
165+ let ( :deployment ) do
166+ DeploymentModel . make (
167+ app : web_process . app ,
168+ deploying_web_process : deploying_web_process ,
169+ state : state ,
170+ strategy : 'canary' ,
171+ original_web_process_instance_count : original_web_process_instance_count ,
172+ max_in_flight : 1
173+ )
174+ end
175+
176+ describe 'canary steps' do
177+ let ( :max_in_flight ) { 1 }
178+ let ( :original_web_process_instance_count ) { 10 }
179+ let ( :deployment ) do
180+ DeploymentModel . make (
181+ app : web_process . app ,
182+ deploying_web_process : deploying_web_process ,
183+ strategy : 'canary' ,
184+ droplet : droplet ,
185+ state : state ,
186+ max_in_flight : max_in_flight ,
187+ original_web_process_instance_count : 10 ,
188+ canary_steps : [ { instance_weight : 50 } , { instance_weight : 80 } ] ,
189+ canary_current_step : 1
190+ )
191+ end
192+
193+ context 'when the current step instance count has been reached' do
194+ let ( :current_deploying_instances ) { 5 }
195+ let ( :all_instances_results ) do
196+ {
197+ 0 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
198+ 1 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
199+ 2 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
200+ 3 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
201+ 4 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true }
202+ }
203+ end
204+
205+ it 'transitions state to paused' do
206+ subject . canary
207+ expect ( deployment . state ) . to eq ( DeploymentModel ::PAUSED_STATE )
208+ end
209+ end
210+
211+ context 'when the current step instance count has not been reached' do
212+ let ( :current_deploying_instances ) { 4 }
213+ let ( :all_instances_results ) do
214+ {
215+ 0 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
216+ 1 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
217+ 2 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
218+ 3 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true }
219+ }
220+ end
221+
222+ it 'does not transition to paused' do
223+ subject . canary
224+ expect ( deployment . state ) . not_to eq ( DeploymentModel ::PAUSED_STATE )
225+ end
226+ end
227+
228+ context 'when there is a single unhealthy instance' do
229+ let ( :current_deploying_instances ) { 5 }
230+ let ( :all_instances_results ) do
231+ {
232+ 0 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
233+ 1 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
234+ 2 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
235+ 3 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : false } ,
236+ 4 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true }
237+ }
238+ end
239+
240+ it 'does not transition to paused' do
241+ subject . canary
242+ expect ( deployment . state ) . not_to eq ( DeploymentModel ::PAUSED_STATE )
243+ end
244+ end
245+
246+ context 'when there are not enough actual instances' do
247+ let ( :current_deploying_instances ) { 5 }
248+ let ( :all_instances_results ) do
249+ {
250+ 0 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
251+ 1 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
252+ 2 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true } ,
253+ 3 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : true }
254+ }
255+ end
256+
257+ it 'does not transition to paused' do
258+ subject . canary
259+ expect ( deployment . state ) . not_to eq ( DeploymentModel ::PAUSED_STATE )
260+ end
261+ end
262+ end
165263
166264 context 'when the canary instance starts succesfully' do
167265 let ( :all_instances_results ) do
@@ -170,6 +268,11 @@ module VCAP::CloudController
170268 }
171269 end
172270
271+ it 'transitions state to paused' do
272+ subject . canary
273+ expect ( deployment . state ) . to eq ( DeploymentModel ::PAUSED_STATE )
274+ end
275+
173276 it 'logs the canary is paused' do
174277 subject . canary
175278 expect ( logger ) . to have_received ( :info ) . with (
@@ -185,6 +288,59 @@ module VCAP::CloudController
185288 end
186289 end
187290
291+ context 'when the canary is not routable' do
292+ let ( :all_instances_results ) do
293+ {
294+ 0 => { state : 'RUNNING' , uptime : 50 , since : 2 , routable : false }
295+ }
296+ end
297+
298+ it 'does not transition state to paused' do
299+ subject . canary
300+ expect ( deployment . state ) . to eq ( DeploymentModel ::PREPAUSED_STATE )
301+ expect ( deployment . status_value ) . to eq ( DeploymentModel ::ACTIVE_STATUS_VALUE )
302+ expect ( deployment . status_reason ) . to eq ( DeploymentModel ::DEPLOYING_STATUS_REASON )
303+ end
304+ end
305+
306+ context 'while the canary instance is still starting' do
307+ let ( :all_instances_results ) do
308+ {
309+ 0 => { state : 'STARTING' , uptime : 50 , since : 2 , routable : true }
310+ }
311+ end
312+
313+ it 'skips the deployment update' do
314+ subject . canary
315+ expect ( deployment . state ) . to eq ( DeploymentModel ::PREPAUSED_STATE )
316+ expect ( deployment . status_value ) . to eq ( DeploymentModel ::ACTIVE_STATUS_VALUE )
317+ expect ( deployment . status_reason ) . to eq ( DeploymentModel ::DEPLOYING_STATUS_REASON )
318+ end
319+ end
320+
321+ context 'when the canary instance is failing' do
322+ let ( :all_instances_results ) do
323+ {
324+ 0 => { state : 'FAILING' , uptime : 50 , since : 2 , routable : true }
325+ }
326+ end
327+
328+ it 'does not update the deployments last_healthy_at' do
329+ Timecop . travel ( Time . now + 1 . minute ) do
330+ expect do
331+ subject . canary
332+ end . not_to ( change { deployment . reload . last_healthy_at } )
333+ end
334+ end
335+
336+ it 'skips the deployment update' do
337+ subject . canary
338+ expect ( deployment . state ) . to eq ( DeploymentModel ::PREPAUSED_STATE )
339+ expect ( deployment . status_value ) . to eq ( DeploymentModel ::ACTIVE_STATUS_VALUE )
340+ expect ( deployment . status_reason ) . to eq ( DeploymentModel ::DEPLOYING_STATUS_REASON )
341+ end
342+ end
343+
188344 context 'when an error occurs while canarying a deployment' do
189345 before do
190346 allow ( deployment ) . to receive ( :lock! ) . and_raise ( StandardError . new ( 'Something real bad happened' ) )
0 commit comments