// Copyright 2015-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. package service import ( "fmt" "sort" "time" "github.com/aws/amazon-ecs-cli/ecs-cli/modules/commands/flags" "github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils/waiters" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ecs" log "github.com/sirupsen/logrus" ) const ( // DefaultUpdateServiceTimeout is the time that the CLI will wait to check if the // count of running tasks is changing. If count has not changed then an error is thrown // after DefaultUpdateServiceTimeout minutes DefaultUpdateServiceTimeout = 5 // latestEventWindow defines "now"- it ensures that we only print events // which were created since roughly when the user entered the command in their // terminal. Units = seconds. latestEventWindow = 2 ) // serviceEvents is a wrapper for []*ecs.ServiceEvent // that allows us to sort it by the timestamp type serviceEvents []*ecs.ServiceEvent func (s serviceEvents) Len() int { return len(s) } func (s serviceEvents) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s serviceEvents) Less(i, j int) bool { time1 := *s[i].CreatedAt time2 := *s[j].CreatedAt return time1.Before(time2) } // logNewServiceEvents logs events that have not been logged yet func logNewServiceEvents(loggedEvents map[string]bool, events []*ecs.ServiceEvent, actionInvokedAt time.Time) { // sort the events so that newer ones are printed last sort.Sort(serviceEvents(events)) for _, event := range events { if _, ok := loggedEvents[*event.Id]; !ok { // New event that has not been logged yet loggedEvents[*event.Id] = true if actionInvokedAt.Sub(*event.CreatedAt).Seconds() < latestEventWindow { log.WithFields(log.Fields{ "timestamp": *event.CreatedAt}, ).Info(aws.StringValue(event.Message)) } } } } func waitForServiceDescribable(service *Service) error { eventsLogged := make(map[string]bool) timeOut := float64(DefaultUpdateServiceTimeout) actionInvokedAt := time.Now() if val := service.Context().CLIContext.Float64(flags.ComposeServiceTimeOutFlag); val > 0 { timeOut = val } else if val < 0 { return fmt.Errorf("Error with timeout flag: %f is not a valid timeout value", val) } else { log.Warn("Timeout was specified as zero. Service creation may not have completed yet.") return nil } return waiters.ServiceWaitUntilComplete(func() (bool, error) { if ecsService, err := service.describeService(); err == nil { // log new service events if len(ecsService.Events) > 0 { logNewServiceEvents(eventsLogged, ecsService.Events, actionInvokedAt) } return true, nil } if time.Since(actionInvokedAt).Minutes() > timeOut { return false, fmt.Errorf("Deployment has not completed: Service can't be described after %.2f minutes", timeOut) } return false, nil }, service) } // waitForServiceTasks continuously polls ECS (by calling describeService) and // waits for service to get stable with desiredCount == runningCount func waitForServiceTasks(service *Service, ecsServiceName string) error { eventsLogged := make(map[string]bool) var lastRunningCount int64 lastRunningCountChangedAt := time.Now() timeOut := float64(DefaultUpdateServiceTimeout) actionInvokedAt := time.Now() if val := service.Context().CLIContext.Float64(flags.ComposeServiceTimeOutFlag); val > 0 { timeOut = val } else if val < 0 { return fmt.Errorf("Error with timeout flag: %f is not a valid timeout value", val) } else { log.Warn("Timeout was specified as zero. Your service deployment may not have completed yet.") return nil } return waiters.ServiceWaitUntilComplete(func() (bool, error) { ecsService, err := service.describeService() if err != nil { return false, err } desiredCount := aws.Int64Value(ecsService.DesiredCount) runningCount := aws.Int64Value(ecsService.RunningCount) // log current state of service logFields := log.Fields{ "serviceName": ecsServiceName, "desiredCount": desiredCount, "runningCount": runningCount, } // Log if running count has changed if runningCount != lastRunningCount { lastRunningCount = runningCount lastRunningCountChangedAt = time.Now() log.WithFields(logFields).Info("Service status") } // log new service events if len(ecsService.Events) > 0 { logNewServiceEvents(eventsLogged, ecsService.Events, actionInvokedAt) } // The deployment was successful if len(ecsService.Deployments) == 1 && desiredCount == runningCount { log.WithFields(logFields).Info("ECS Service has reached a stable state") return true, nil } if time.Since(lastRunningCountChangedAt).Minutes() > timeOut { return false, fmt.Errorf("Deployment has not completed: Running count has not changed for %.2f minutes", timeOut) } return false, nil }, service) }