Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Lehmann authored on 2017/01/19 07:16:47... | ... |
@@ -175,6 +175,115 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesUpdate(c *check.C) { |
175 | 175 |
map[string]int{image1: instances}) |
176 | 176 |
} |
177 | 177 |
|
178 |
+func (s *DockerSwarmSuite) TestAPISwarmServicesUpdateStartFirst(c *check.C) { |
|
179 |
+ d := s.AddDaemon(c, true, true) |
|
180 |
+ |
|
181 |
+ // service image at start |
|
182 |
+ image1 := "busybox:latest" |
|
183 |
+ // target image in update |
|
184 |
+ image2 := "testhealth" |
|
185 |
+ |
|
186 |
+ // service started from this image won't pass health check |
|
187 |
+ _, _, err := d.BuildImageWithOut(image2, |
|
188 |
+ `FROM busybox |
|
189 |
+ HEALTHCHECK --interval=1s --timeout=1s --retries=1024\ |
|
190 |
+ CMD cat /status`, |
|
191 |
+ true) |
|
192 |
+ c.Check(err, check.IsNil) |
|
193 |
+ |
|
194 |
+ // create service |
|
195 |
+ instances := 5 |
|
196 |
+ parallelism := 2 |
|
197 |
+ rollbackParallelism := 3 |
|
198 |
+ id := d.CreateService(c, serviceForUpdate, setInstances(instances), setUpdateOrder(swarm.UpdateOrderStartFirst), setRollbackOrder(swarm.UpdateOrderStartFirst)) |
|
199 |
+ |
|
200 |
+ checkStartingTasks := func(expected int) []swarm.Task { |
|
201 |
+ var startingTasks []swarm.Task |
|
202 |
+ waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { |
|
203 |
+ tasks := d.GetServiceTasks(c, id) |
|
204 |
+ startingTasks = nil |
|
205 |
+ for _, t := range tasks { |
|
206 |
+ if t.Status.State == swarm.TaskStateStarting { |
|
207 |
+ startingTasks = append(startingTasks, t) |
|
208 |
+ } |
|
209 |
+ } |
|
210 |
+ return startingTasks, nil |
|
211 |
+ }, checker.HasLen, expected) |
|
212 |
+ |
|
213 |
+ return startingTasks |
|
214 |
+ } |
|
215 |
+ |
|
216 |
+ makeTasksHealthy := func(tasks []swarm.Task) { |
|
217 |
+ for _, t := range tasks { |
|
218 |
+ containerID := t.Status.ContainerStatus.ContainerID |
|
219 |
+ d.Cmd("exec", containerID, "touch", "/status") |
|
220 |
+ } |
|
221 |
+ } |
|
222 |
+ |
|
223 |
+ // wait for tasks ready |
|
224 |
+ waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, |
|
225 |
+ map[string]int{image1: instances}) |
|
226 |
+ |
|
227 |
+ // issue service update |
|
228 |
+ service := d.GetService(c, id) |
|
229 |
+ d.UpdateService(c, service, setImage(image2)) |
|
230 |
+ |
|
231 |
+ // first batch |
|
232 |
+ |
|
233 |
+ // The old tasks should be running, and the new ones should be starting. |
|
234 |
+ startingTasks := checkStartingTasks(parallelism) |
|
235 |
+ |
|
236 |
+ waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, |
|
237 |
+ map[string]int{image1: instances}) |
|
238 |
+ |
|
239 |
+ // make it healthy |
|
240 |
+ makeTasksHealthy(startingTasks) |
|
241 |
+ |
|
242 |
+ waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, |
|
243 |
+ map[string]int{image1: instances - parallelism, image2: parallelism}) |
|
244 |
+ |
|
245 |
+ // 2nd batch |
|
246 |
+ |
|
247 |
+ // The old tasks should be running, and the new ones should be starting. |
|
248 |
+ startingTasks = checkStartingTasks(parallelism) |
|
249 |
+ |
|
250 |
+ waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, |
|
251 |
+ map[string]int{image1: instances - parallelism, image2: parallelism}) |
|
252 |
+ |
|
253 |
+ // make it healthy |
|
254 |
+ makeTasksHealthy(startingTasks) |
|
255 |
+ |
|
256 |
+ waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, |
|
257 |
+ map[string]int{image1: instances - 2*parallelism, image2: 2 * parallelism}) |
|
258 |
+ |
|
259 |
+ // 3nd batch |
|
260 |
+ |
|
261 |
+ // The old tasks should be running, and the new ones should be starting. |
|
262 |
+ startingTasks = checkStartingTasks(1) |
|
263 |
+ |
|
264 |
+ waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, |
|
265 |
+ map[string]int{image1: instances - 2*parallelism, image2: 2 * parallelism}) |
|
266 |
+ |
|
267 |
+ // make it healthy |
|
268 |
+ makeTasksHealthy(startingTasks) |
|
269 |
+ |
|
270 |
+ waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, |
|
271 |
+ map[string]int{image2: instances}) |
|
272 |
+ |
|
273 |
+ // Roll back to the previous version. This uses the CLI because |
|
274 |
+ // rollback is a client-side operation. |
|
275 |
+ out, err := d.Cmd("service", "update", "--rollback", id) |
|
276 |
+ c.Assert(err, checker.IsNil, check.Commentf(out)) |
|
277 |
+ |
|
278 |
+ // first batch |
|
279 |
+ waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, |
|
280 |
+ map[string]int{image2: instances - rollbackParallelism, image1: rollbackParallelism}) |
|
281 |
+ |
|
282 |
+ // 2nd batch |
|
283 |
+ waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, |
|
284 |
+ map[string]int{image1: instances}) |
|
285 |
+} |
|
286 |
+ |
|
178 | 287 |
func (s *DockerSwarmSuite) TestAPISwarmServicesFailedUpdate(c *check.C) { |
179 | 288 |
const nodeCount = 3 |
180 | 289 |
var daemons [nodeCount]*daemon.Swarm |
... | ... |
@@ -596,6 +596,24 @@ func setInstances(replicas int) daemon.ServiceConstructor { |
596 | 596 |
} |
597 | 597 |
} |
598 | 598 |
|
599 |
+func setUpdateOrder(order string) daemon.ServiceConstructor { |
|
600 |
+ return func(s *swarm.Service) { |
|
601 |
+ if s.Spec.UpdateConfig == nil { |
|
602 |
+ s.Spec.UpdateConfig = &swarm.UpdateConfig{} |
|
603 |
+ } |
|
604 |
+ s.Spec.UpdateConfig.Order = order |
|
605 |
+ } |
|
606 |
+} |
|
607 |
+ |
|
608 |
+func setRollbackOrder(order string) daemon.ServiceConstructor { |
|
609 |
+ return func(s *swarm.Service) { |
|
610 |
+ if s.Spec.RollbackConfig == nil { |
|
611 |
+ s.Spec.RollbackConfig = &swarm.UpdateConfig{} |
|
612 |
+ } |
|
613 |
+ s.Spec.RollbackConfig.Order = order |
|
614 |
+ } |
|
615 |
+} |
|
616 |
+ |
|
599 | 617 |
func setImage(image string) daemon.ServiceConstructor { |
600 | 618 |
return func(s *swarm.Service) { |
601 | 619 |
s.Spec.TaskTemplate.ContainerSpec.Image = image |