github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/engine/engine.go (about)

     1  package engine
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  	"time"
     9  
    10  	"code.cloudfoundry.org/lager"
    11  	"code.cloudfoundry.org/lager/lagerctx"
    12  	"github.com/pf-qiu/concourse/v6/atc"
    13  	"github.com/pf-qiu/concourse/v6/atc/creds"
    14  	"github.com/pf-qiu/concourse/v6/atc/db"
    15  	"github.com/pf-qiu/concourse/v6/atc/event"
    16  	"github.com/pf-qiu/concourse/v6/atc/exec"
    17  	"github.com/pf-qiu/concourse/v6/atc/metric"
    18  	"github.com/pf-qiu/concourse/v6/atc/util"
    19  	"github.com/pf-qiu/concourse/v6/tracing"
    20  )
    21  
    22  //go:generate counterfeiter . Engine
    23  
    24  type Engine interface {
    25  	NewBuild(db.Build) Runnable
    26  
    27  	Drain(context.Context)
    28  }
    29  
    30  //go:generate counterfeiter . Runnable
    31  
    32  type Runnable interface {
    33  	Run(context.Context)
    34  }
    35  
    36  func NewEngine(
    37  	stepperFactory StepperFactory,
    38  	secrets creds.Secrets,
    39  	varSourcePool creds.VarSourcePool,
    40  ) Engine {
    41  	return &engine{
    42  		stepperFactory: stepperFactory,
    43  		release:        make(chan bool),
    44  		trackedStates:  new(sync.Map),
    45  		waitGroup:      new(sync.WaitGroup),
    46  
    47  		globalSecrets: secrets,
    48  		varSourcePool: varSourcePool,
    49  	}
    50  }
    51  
    52  type engine struct {
    53  	stepperFactory StepperFactory
    54  	release        chan bool
    55  	trackedStates  *sync.Map
    56  	waitGroup      *sync.WaitGroup
    57  
    58  	globalSecrets creds.Secrets
    59  	varSourcePool creds.VarSourcePool
    60  }
    61  
    62  func (engine *engine) Drain(ctx context.Context) {
    63  	logger := lagerctx.FromContext(ctx)
    64  
    65  	logger.Info("start")
    66  	defer logger.Info("done")
    67  
    68  	close(engine.release)
    69  
    70  	logger.Info("waiting")
    71  
    72  	engine.waitGroup.Wait()
    73  }
    74  
    75  func (engine *engine) NewBuild(build db.Build) Runnable {
    76  	return NewBuild(
    77  		build,
    78  		engine.stepperFactory,
    79  		engine.globalSecrets,
    80  		engine.varSourcePool,
    81  		engine.release,
    82  		engine.trackedStates,
    83  		engine.waitGroup,
    84  	)
    85  }
    86  
    87  func NewBuild(
    88  	build db.Build,
    89  	builder StepperFactory,
    90  	globalSecrets creds.Secrets,
    91  	varSourcePool creds.VarSourcePool,
    92  	release chan bool,
    93  	trackedStates *sync.Map,
    94  	waitGroup *sync.WaitGroup,
    95  ) Runnable {
    96  	return &engineBuild{
    97  		build:   build,
    98  		builder: builder,
    99  
   100  		globalSecrets: globalSecrets,
   101  		varSourcePool: varSourcePool,
   102  
   103  		release:       release,
   104  		trackedStates: trackedStates,
   105  		waitGroup:     waitGroup,
   106  	}
   107  }
   108  
   109  type engineBuild struct {
   110  	build   db.Build
   111  	builder StepperFactory
   112  
   113  	globalSecrets creds.Secrets
   114  	varSourcePool creds.VarSourcePool
   115  
   116  	release       chan bool
   117  	trackedStates *sync.Map
   118  	waitGroup     *sync.WaitGroup
   119  }
   120  
   121  func (b *engineBuild) Run(ctx context.Context) {
   122  	b.waitGroup.Add(1)
   123  	defer b.waitGroup.Done()
   124  
   125  	logger := lagerctx.FromContext(ctx).WithData(b.build.LagerData())
   126  
   127  	lock, acquired, err := b.build.AcquireTrackingLock(logger, time.Minute)
   128  	if err != nil {
   129  		logger.Error("failed-to-get-lock", err)
   130  		return
   131  	}
   132  
   133  	if !acquired {
   134  		logger.Debug("build-already-tracked")
   135  		return
   136  	}
   137  
   138  	defer lock.Release()
   139  
   140  	found, err := b.build.Reload()
   141  	if err != nil {
   142  		logger.Error("failed-to-load-build-from-db", err)
   143  		return
   144  	}
   145  
   146  	if !found {
   147  		logger.Info("build-not-found")
   148  		return
   149  	}
   150  
   151  	if !b.build.IsRunning() {
   152  		logger.Info("build-already-finished")
   153  		return
   154  	}
   155  
   156  	notifier, err := b.build.AbortNotifier()
   157  	if err != nil {
   158  		logger.Error("failed-to-listen-for-aborts", err)
   159  		return
   160  	}
   161  
   162  	defer notifier.Close()
   163  
   164  	ctx, span := tracing.StartSpanFollowing(ctx, b.build, "build", b.build.TracingAttrs())
   165  	defer span.End()
   166  
   167  	stepper, err := b.builder.StepperForBuild(b.build)
   168  	if err != nil {
   169  		logger.Error("failed-to-construct-build-stepper", err)
   170  
   171  		// Fails the build if BuildStep returned an error because such unrecoverable
   172  		// errors will cause a build to never start to run.
   173  		b.buildStepErrored(logger, err.Error())
   174  		b.finish(logger.Session("finish"), err, false)
   175  
   176  		return
   177  	}
   178  
   179  	b.trackStarted(logger)
   180  	defer b.trackFinished(logger)
   181  
   182  	logger.Info("running")
   183  
   184  	state, err := b.runState(logger, stepper)
   185  	if err != nil {
   186  		logger.Error("failed-to-create-run-state", err)
   187  
   188  		// Fails the build if fetching the pipeline variables fails, as these errors
   189  		// are unrecoverable - e.g. if pipeline var_sources is wrong
   190  		b.buildStepErrored(logger, err.Error())
   191  		b.finish(logger.Session("finish"), err, false)
   192  
   193  		return
   194  	}
   195  	defer b.clearRunState()
   196  
   197  	ctx, cancel := context.WithCancel(ctx)
   198  
   199  	noleak := make(chan bool)
   200  	defer close(noleak)
   201  
   202  	go func() {
   203  		select {
   204  		case <-noleak:
   205  		case <-notifier.Notify():
   206  			logger.Info("aborting")
   207  			cancel()
   208  		}
   209  	}()
   210  
   211  	var succeeded bool
   212  	var runErr error
   213  
   214  	done := make(chan struct{})
   215  	go func() {
   216  		defer close(done)
   217  		defer func() {
   218  			err := util.DumpPanic(recover(), "running build plan %d", b.build.ID())
   219  			if err != nil {
   220  				logger.Error("panic-in-engine-build-step-run", err)
   221  				runErr = err
   222  			}
   223  		}()
   224  
   225  		succeeded, runErr = state.Run(lagerctx.NewContext(ctx, logger), b.build.PrivatePlan())
   226  	}()
   227  
   228  	select {
   229  	case <-b.release:
   230  		logger.Info("releasing")
   231  
   232  	case <-done:
   233  		if errors.As(runErr, &exec.Retriable{}) {
   234  			return
   235  		}
   236  
   237  		b.finish(logger.Session("finish"), runErr, succeeded)
   238  	}
   239  }
   240  
   241  func (b *engineBuild) buildStepErrored(logger lager.Logger, message string) {
   242  	err := b.build.SaveEvent(event.Error{
   243  		Message: message,
   244  		Origin: event.Origin{
   245  			ID: event.OriginID(b.build.PrivatePlan().ID),
   246  		},
   247  		Time: time.Now().Unix(),
   248  	})
   249  	if err != nil {
   250  		logger.Error("failed-to-save-error-event", err)
   251  	}
   252  }
   253  
   254  func (b *engineBuild) finish(logger lager.Logger, err error, succeeded bool) {
   255  	if errors.Is(err, context.Canceled) {
   256  		b.saveStatus(logger, atc.StatusAborted)
   257  		logger.Info("aborted")
   258  
   259  	} else if err != nil {
   260  		b.saveStatus(logger, atc.StatusErrored)
   261  		logger.Info("errored", lager.Data{"error": err.Error()})
   262  
   263  	} else if succeeded {
   264  		b.saveStatus(logger, atc.StatusSucceeded)
   265  		logger.Info("succeeded")
   266  
   267  	} else {
   268  		b.saveStatus(logger, atc.StatusFailed)
   269  		logger.Info("failed")
   270  	}
   271  }
   272  
   273  func (b *engineBuild) saveStatus(logger lager.Logger, status atc.BuildStatus) {
   274  	if err := b.build.Finish(db.BuildStatus(status)); err != nil {
   275  		logger.Error("failed-to-finish-build", err)
   276  	}
   277  }
   278  
   279  func (b *engineBuild) trackStarted(logger lager.Logger) {
   280  	metric.BuildStarted{
   281  		Build: b.build,
   282  	}.Emit(logger)
   283  }
   284  
   285  func (b *engineBuild) trackFinished(logger lager.Logger) {
   286  	found, err := b.build.Reload()
   287  	if err != nil {
   288  		logger.Error("failed-to-load-build-from-db", err)
   289  		return
   290  	}
   291  
   292  	if !found {
   293  		logger.Info("build-removed")
   294  		return
   295  	}
   296  
   297  	if !b.build.IsRunning() {
   298  		metric.BuildFinished{
   299  			Build: b.build,
   300  		}.Emit(logger)
   301  	}
   302  }
   303  
   304  func (b *engineBuild) runState(logger lager.Logger, stepper exec.Stepper) (exec.RunState, error) {
   305  	id := fmt.Sprintf("build:%v", b.build.ID())
   306  	existingState, ok := b.trackedStates.Load(id)
   307  	if ok {
   308  		return existingState.(exec.RunState), nil
   309  	}
   310  	credVars, err := b.build.Variables(logger, b.globalSecrets, b.varSourcePool)
   311  	if err != nil {
   312  		return nil, err
   313  	}
   314  	state, _ := b.trackedStates.LoadOrStore(id, exec.NewRunState(stepper, credVars, atc.EnableRedactSecrets))
   315  	return state.(exec.RunState), nil
   316  }
   317  
   318  func (b *engineBuild) clearRunState() {
   319  	id := fmt.Sprintf("build:%v", b.build.ID())
   320  	b.trackedStates.Delete(id)
   321  }