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

     1  package worker
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math/rand"
     8  	"time"
     9  
    10  	"code.cloudfoundry.org/lager"
    11  
    12  	"github.com/pf-qiu/concourse/v6/atc/db"
    13  )
    14  
    15  //go:generate counterfeiter . WorkerProvider
    16  
    17  type WorkerProvider interface {
    18  	RunningWorkers(lager.Logger) ([]Worker, error)
    19  
    20  	FindWorkerForContainer(
    21  		logger lager.Logger,
    22  		teamID int,
    23  		handle string,
    24  	) (Worker, bool, error)
    25  
    26  	FindWorkerForVolume(
    27  		logger lager.Logger,
    28  		teamID int,
    29  		handle string,
    30  	) (Worker, bool, error)
    31  
    32  	FindWorkersForContainerByOwner(
    33  		logger lager.Logger,
    34  		owner db.ContainerOwner,
    35  	) ([]Worker, error)
    36  
    37  	NewGardenWorker(
    38  		logger lager.Logger,
    39  		savedWorker db.Worker,
    40  		numBuildWorkers int,
    41  	) Worker
    42  }
    43  
    44  var (
    45  	ErrNoWorkers             = errors.New("no workers")
    46  	ErrFailedAcquirePoolLock = errors.New("failed to acquire pool lock")
    47  )
    48  
    49  type NoCompatibleWorkersError struct {
    50  	Spec WorkerSpec
    51  }
    52  
    53  func (err NoCompatibleWorkersError) Error() string {
    54  	return fmt.Sprintf("no workers satisfying: %s", err.Spec.Description())
    55  }
    56  
    57  //go:generate counterfeiter . Pool
    58  
    59  type Pool interface {
    60  	FindOrChooseWorker(
    61  		lager.Logger,
    62  		WorkerSpec,
    63  	) (Worker, error)
    64  
    65  	ContainerInWorker(
    66  		lager.Logger,
    67  		db.ContainerOwner,
    68  		WorkerSpec,
    69  	) (bool, error)
    70  
    71  	FindOrChooseWorkerForContainer(
    72  		context.Context,
    73  		lager.Logger,
    74  		db.ContainerOwner,
    75  		ContainerSpec,
    76  		WorkerSpec,
    77  		ContainerPlacementStrategy,
    78  	) (Worker, error)
    79  }
    80  
    81  type pool struct {
    82  	provider WorkerProvider
    83  	rand     *rand.Rand
    84  }
    85  
    86  func NewPool(
    87  	provider WorkerProvider,
    88  ) Pool {
    89  	return &pool{
    90  		provider: provider,
    91  		rand:     rand.New(rand.NewSource(time.Now().UnixNano())),
    92  	}
    93  }
    94  
    95  func (pool *pool) allSatisfying(logger lager.Logger, spec WorkerSpec) ([]Worker, error) {
    96  	workers, err := pool.provider.RunningWorkers(logger)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	if len(workers) == 0 {
   102  		return nil, ErrNoWorkers
   103  	}
   104  
   105  	compatibleTeamWorkers := []Worker{}
   106  	compatibleGeneralWorkers := []Worker{}
   107  	for _, worker := range workers {
   108  		compatible := worker.Satisfies(logger, spec)
   109  		if compatible {
   110  			if worker.IsOwnedByTeam() {
   111  				compatibleTeamWorkers = append(compatibleTeamWorkers, worker)
   112  			} else {
   113  				compatibleGeneralWorkers = append(compatibleGeneralWorkers, worker)
   114  			}
   115  		}
   116  	}
   117  
   118  	if len(compatibleTeamWorkers) != 0 {
   119  		return compatibleTeamWorkers, nil
   120  	}
   121  
   122  	if len(compatibleGeneralWorkers) != 0 {
   123  		return compatibleGeneralWorkers, nil
   124  	}
   125  
   126  	return nil, NoCompatibleWorkersError{
   127  		Spec: spec,
   128  	}
   129  }
   130  
   131  func (pool *pool) ContainerInWorker(logger lager.Logger, owner db.ContainerOwner, workerSpec WorkerSpec) (bool, error) {
   132  	workersWithContainer, err := pool.provider.FindWorkersForContainerByOwner(
   133  		logger.Session("find-worker"),
   134  		owner,
   135  	)
   136  	if err != nil {
   137  		return false, err
   138  	}
   139  
   140  	compatibleWorkers, err := pool.allSatisfying(logger, workerSpec)
   141  	if err != nil {
   142  		return false, err
   143  	}
   144  
   145  	for _, w := range workersWithContainer {
   146  		for _, c := range compatibleWorkers {
   147  			if w.Name() == c.Name() {
   148  				return true, nil
   149  			}
   150  		}
   151  	}
   152  
   153  	return false, nil
   154  }
   155  
   156  func (pool *pool) FindOrChooseWorkerForContainer(
   157  	ctx context.Context,
   158  	logger lager.Logger,
   159  	owner db.ContainerOwner,
   160  	containerSpec ContainerSpec,
   161  	workerSpec WorkerSpec,
   162  	strategy ContainerPlacementStrategy,
   163  ) (Worker, error) {
   164  	workersWithContainer, err := pool.provider.FindWorkersForContainerByOwner(
   165  		logger.Session("find-worker"),
   166  		owner,
   167  	)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	compatibleWorkers, err := pool.allSatisfying(logger, workerSpec)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	var worker Worker
   178  dance:
   179  	for _, w := range workersWithContainer {
   180  		for _, c := range compatibleWorkers {
   181  			if w.Name() == c.Name() {
   182  				worker = c
   183  				break dance
   184  			}
   185  		}
   186  	}
   187  
   188  	if worker == nil {
   189  		worker, err = strategy.Choose(logger, compatibleWorkers, containerSpec)
   190  		if err != nil {
   191  			return nil, err
   192  		}
   193  	}
   194  
   195  	return worker, nil
   196  }
   197  
   198  func (pool *pool) FindOrChooseWorker(
   199  	logger lager.Logger,
   200  	workerSpec WorkerSpec,
   201  ) (Worker, error) {
   202  	workers, err := pool.allSatisfying(logger, workerSpec)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	return workers[rand.Intn(len(workers))], nil
   208  }