agones.dev/agones@v1.53.0/pkg/gameserversets/controller.go (about)

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gameserversets
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"sync"
    21  	"time"
    22  
    23  	"agones.dev/agones/pkg/apis"
    24  	"agones.dev/agones/pkg/apis/agones"
    25  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    26  	"agones.dev/agones/pkg/client/clientset/versioned"
    27  	getterv1 "agones.dev/agones/pkg/client/clientset/versioned/typed/agones/v1"
    28  	"agones.dev/agones/pkg/client/informers/externalversions"
    29  	listerv1 "agones.dev/agones/pkg/client/listers/agones/v1"
    30  	"agones.dev/agones/pkg/gameservers"
    31  	"agones.dev/agones/pkg/util/crd"
    32  	"agones.dev/agones/pkg/util/logfields"
    33  	"agones.dev/agones/pkg/util/runtime"
    34  	"agones.dev/agones/pkg/util/webhooks"
    35  	"agones.dev/agones/pkg/util/workerqueue"
    36  	"github.com/google/go-cmp/cmp"
    37  	"github.com/heptiolabs/healthcheck"
    38  	"github.com/pkg/errors"
    39  	"github.com/sirupsen/logrus"
    40  	"go.opencensus.io/tag"
    41  	admissionv1 "k8s.io/api/admission/v1"
    42  	corev1 "k8s.io/api/core/v1"
    43  	extclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    44  	apiextclientv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
    45  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    46  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    47  	runtimeschema "k8s.io/apimachinery/pkg/runtime/schema"
    48  	"k8s.io/client-go/kubernetes"
    49  	"k8s.io/client-go/kubernetes/scheme"
    50  	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    51  	"k8s.io/client-go/tools/cache"
    52  	"k8s.io/client-go/tools/record"
    53  )
    54  
    55  var (
    56  	// ErrNoGameServerSetOwner is returned when a GameServerSet can't be found as an owner
    57  	// for a GameServer
    58  	ErrNoGameServerSetOwner = errors.New("No GameServerSet owner for this GameServer")
    59  )
    60  
    61  const (
    62  	// gameServerErrorDeletionDelay is the minimum amount of time to delay the deletion
    63  	// of a GameServer in Error state.
    64  	gameServerErrorDeletionDelay = 30 * time.Second
    65  )
    66  
    67  // Extensions struct contains what is needed to bind webhook handlers
    68  type Extensions struct {
    69  	baseLogger *logrus.Entry
    70  	apiHooks   agonesv1.APIHooks
    71  }
    72  
    73  func init() {
    74  	registerViews()
    75  }
    76  
    77  // Controller is a GameServerSet controller
    78  type Controller struct {
    79  	baseLogger                     *logrus.Entry
    80  	counter                        *gameservers.PerNodeCounter
    81  	crdGetter                      apiextclientv1.CustomResourceDefinitionInterface
    82  	gameServerGetter               getterv1.GameServersGetter
    83  	gameServerLister               listerv1.GameServerLister
    84  	gameServerSynced               cache.InformerSynced
    85  	gameServerSetGetter            getterv1.GameServerSetsGetter
    86  	gameServerSetLister            listerv1.GameServerSetLister
    87  	gameServerSetSynced            cache.InformerSynced
    88  	workerqueue                    *workerqueue.WorkerQueue
    89  	recorder                       record.EventRecorder
    90  	stateCache                     *gameServerStateCache
    91  	allocationController           *AllocationOverflowController
    92  	maxCreationParallelism         int
    93  	maxGameServerCreationsPerBatch int
    94  	maxDeletionParallelism         int
    95  	maxGameServerDeletionsPerBatch int
    96  	maxPodPendingCount             int
    97  }
    98  
    99  // NewController returns a new gameserverset crd controller
   100  func NewController(
   101  	health healthcheck.Handler,
   102  	counter *gameservers.PerNodeCounter,
   103  	kubeClient kubernetes.Interface,
   104  	extClient extclientset.Interface,
   105  	agonesClient versioned.Interface,
   106  	agonesInformerFactory externalversions.SharedInformerFactory,
   107  	maxCreationParallelism int,
   108  	maxDeletionParallelism int,
   109  	maxGameServerCreationsPerBatch int,
   110  	maxGameServerDeletionsPerBatch int,
   111  	maxPodPendingCount int) *Controller {
   112  
   113  	gameServers := agonesInformerFactory.Agones().V1().GameServers()
   114  	gsInformer := gameServers.Informer()
   115  	gameServerSets := agonesInformerFactory.Agones().V1().GameServerSets()
   116  	gsSetInformer := gameServerSets.Informer()
   117  
   118  	c := &Controller{
   119  		crdGetter:                      extClient.ApiextensionsV1().CustomResourceDefinitions(),
   120  		counter:                        counter,
   121  		gameServerGetter:               agonesClient.AgonesV1(),
   122  		gameServerLister:               gameServers.Lister(),
   123  		gameServerSynced:               gsInformer.HasSynced,
   124  		gameServerSetGetter:            agonesClient.AgonesV1(),
   125  		gameServerSetLister:            gameServerSets.Lister(),
   126  		gameServerSetSynced:            gsSetInformer.HasSynced,
   127  		maxCreationParallelism:         maxCreationParallelism,
   128  		maxDeletionParallelism:         maxDeletionParallelism,
   129  		maxGameServerCreationsPerBatch: maxGameServerCreationsPerBatch,
   130  		maxGameServerDeletionsPerBatch: maxGameServerDeletionsPerBatch,
   131  		maxPodPendingCount:             maxPodPendingCount,
   132  		stateCache:                     &gameServerStateCache{},
   133  	}
   134  
   135  	c.baseLogger = runtime.NewLoggerWithType(c)
   136  	c.workerqueue = workerqueue.NewWorkerQueueWithRateLimiter(c.syncGameServerSet, c.baseLogger, logfields.GameServerSetKey, agones.GroupName+".GameServerSetController", workerqueue.FastRateLimiter(3*time.Second))
   137  	health.AddLivenessCheck("gameserverset-workerqueue", healthcheck.Check(c.workerqueue.Healthy))
   138  
   139  	eventBroadcaster := record.NewBroadcaster()
   140  	eventBroadcaster.StartLogging(c.baseLogger.Debugf)
   141  	eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")})
   142  	c.recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "gameserverset-controller"})
   143  
   144  	c.allocationController = NewAllocatorOverflowController(health, counter, agonesClient, agonesInformerFactory)
   145  
   146  	_, _ = gsSetInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
   147  		AddFunc: c.workerqueue.Enqueue,
   148  		UpdateFunc: func(oldObj, newObj interface{}) {
   149  			oldGss := oldObj.(*agonesv1.GameServerSet)
   150  			newGss := newObj.(*agonesv1.GameServerSet)
   151  			if oldGss.Spec.Replicas != newGss.Spec.Replicas {
   152  				c.workerqueue.Enqueue(newGss)
   153  			}
   154  		},
   155  		DeleteFunc: func(gsSet interface{}) {
   156  			c.stateCache.deleteGameServerSet(gsSet.(*agonesv1.GameServerSet))
   157  		},
   158  	})
   159  
   160  	_, _ = gsInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
   161  		AddFunc: c.gameServerEventHandler,
   162  		UpdateFunc: func(_, newObj interface{}) {
   163  			gs := newObj.(*agonesv1.GameServer)
   164  			// ignore if already being deleted
   165  			if gs.ObjectMeta.DeletionTimestamp == nil {
   166  				c.gameServerEventHandler(gs)
   167  			}
   168  		},
   169  		DeleteFunc: c.gameServerEventHandler,
   170  	})
   171  
   172  	return c
   173  }
   174  
   175  // NewExtensions binds the handlers to the webhook outside the initialization of the controller
   176  // initializes a new logger for extensions.
   177  func NewExtensions(apiHooks agonesv1.APIHooks, wh *webhooks.WebHook) *Extensions {
   178  	ext := &Extensions{apiHooks: apiHooks}
   179  
   180  	ext.baseLogger = runtime.NewLoggerWithType(ext)
   181  
   182  	wh.AddHandler("/validate", agonesv1.Kind("GameServerSet"), admissionv1.Create, ext.creationValidationHandler)
   183  	wh.AddHandler("/validate", agonesv1.Kind("GameServerSet"), admissionv1.Update, ext.updateValidationHandler)
   184  
   185  	return ext
   186  }
   187  
   188  // Run the GameServerSet controller. Will block until stop is closed.
   189  // Runs threadiness number workers to process the rate limited queue
   190  func (c *Controller) Run(ctx context.Context, workers int) error {
   191  	err := crd.WaitForEstablishedCRD(ctx, c.crdGetter, "gameserversets."+agones.GroupName, c.baseLogger)
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	c.baseLogger.Debug("Wait for cache sync")
   197  	if !cache.WaitForCacheSync(ctx.Done(), c.gameServerSynced, c.gameServerSetSynced) {
   198  		return errors.New("failed to wait for caches to sync")
   199  	}
   200  
   201  	go func() {
   202  		if err := c.allocationController.Run(ctx); err != nil {
   203  			c.baseLogger.WithError(err).Error("error running allocation overflow controller")
   204  		}
   205  	}()
   206  
   207  	c.workerqueue.Run(ctx, workers)
   208  	return nil
   209  }
   210  
   211  // updateValidationHandler that validates a GameServerSet when is updated
   212  // Should only be called on gameserverset update operations.
   213  func (ext *Extensions) updateValidationHandler(review admissionv1.AdmissionReview) (admissionv1.AdmissionReview, error) {
   214  	ext.baseLogger.WithField("review", review).Debug("updateValidationHandler")
   215  
   216  	newGss := &agonesv1.GameServerSet{}
   217  	oldGss := &agonesv1.GameServerSet{}
   218  
   219  	newObj := review.Request.Object
   220  	if err := json.Unmarshal(newObj.Raw, newGss); err != nil {
   221  		return review, errors.Wrapf(err, "error unmarshalling new GameServerSet json: %s", newObj.Raw)
   222  	}
   223  
   224  	oldObj := review.Request.OldObject
   225  	if err := json.Unmarshal(oldObj.Raw, oldGss); err != nil {
   226  		return review, errors.Wrapf(err, "error unmarshalling old GameServerSet json: %s", oldObj.Raw)
   227  	}
   228  
   229  	if errs := oldGss.ValidateUpdate(newGss); len(errs) > 0 {
   230  		kind := runtimeschema.GroupKind{
   231  			Group: review.Request.Kind.Group,
   232  			Kind:  review.Request.Kind.Kind,
   233  		}
   234  		statusErr := k8serrors.NewInvalid(kind, review.Request.Name, errs)
   235  		review.Response.Allowed = false
   236  		review.Response.Result = &statusErr.ErrStatus
   237  		loggerForGameServerSet(ext.baseLogger, newGss).WithField("review", review).Debug("Invalid GameServerSet update")
   238  	}
   239  
   240  	return review, nil
   241  }
   242  
   243  // creationValidationHandler that validates a GameServerSet when is created
   244  // Should only be called on gameserverset create operations.
   245  func (ext *Extensions) creationValidationHandler(review admissionv1.AdmissionReview) (admissionv1.AdmissionReview, error) {
   246  	ext.baseLogger.WithField("review", review).Debug("creationValidationHandler")
   247  
   248  	newGss := &agonesv1.GameServerSet{}
   249  
   250  	newObj := review.Request.Object
   251  	if err := json.Unmarshal(newObj.Raw, newGss); err != nil {
   252  		return review, errors.Wrapf(err, "error unmarshalling GameServerSet json after schema validation: %s", newObj.Raw)
   253  	}
   254  
   255  	if errs := newGss.Validate(ext.apiHooks); len(errs) > 0 {
   256  		kind := runtimeschema.GroupKind{
   257  			Group: review.Request.Kind.Group,
   258  			Kind:  review.Request.Kind.Kind,
   259  		}
   260  		statusErr := k8serrors.NewInvalid(kind, review.Request.Name, errs)
   261  		review.Response.Allowed = false
   262  		review.Response.Result = &statusErr.ErrStatus
   263  		loggerForGameServerSet(ext.baseLogger, newGss).WithField("review", review).Debug("Invalid GameServerSet update")
   264  	}
   265  
   266  	return review, nil
   267  }
   268  
   269  func (c *Controller) gameServerEventHandler(obj interface{}) {
   270  	gs, ok := obj.(*agonesv1.GameServer)
   271  	if !ok {
   272  		return
   273  	}
   274  
   275  	ref := metav1.GetControllerOf(gs)
   276  	if ref == nil {
   277  		return
   278  	}
   279  	gsSet, err := c.gameServerSetLister.GameServerSets(gs.ObjectMeta.Namespace).Get(ref.Name)
   280  	if err != nil {
   281  		if k8serrors.IsNotFound(err) {
   282  			c.baseLogger.WithField("ref", ref).Debug("Owner GameServerSet no longer available for syncing")
   283  		} else {
   284  			runtime.HandleError(c.baseLogger.WithField("gsKey", gs.ObjectMeta.Namespace+"/"+gs.ObjectMeta.Name).WithField("ref", ref),
   285  				errors.Wrap(err, "error retrieving GameServer owner"))
   286  		}
   287  		return
   288  	}
   289  	c.workerqueue.EnqueueImmediately(gsSet)
   290  }
   291  
   292  // syncGameServer synchronises the GameServers for the Set,
   293  // making sure there are aways as many GameServers as requested
   294  func (c *Controller) syncGameServerSet(ctx context.Context, key string) error {
   295  	// Convert the namespace/name string into a distinct namespace and name
   296  	namespace, name, err := cache.SplitMetaNamespaceKey(key)
   297  	if err != nil {
   298  		// don't return an error, as we don't want this retried
   299  		runtime.HandleError(loggerForGameServerSetKey(c.baseLogger, key), errors.Wrapf(err, "invalid resource key"))
   300  		return nil
   301  	}
   302  
   303  	gsSet, err := c.gameServerSetLister.GameServerSets(namespace).Get(name)
   304  	if err != nil {
   305  		if k8serrors.IsNotFound(err) {
   306  			loggerForGameServerSetKey(c.baseLogger, key).Debug("GameServerSet is no longer available for syncing")
   307  			return nil
   308  		}
   309  		return errors.Wrapf(err, "error retrieving GameServerSet %s from namespace %s", name, namespace)
   310  	}
   311  
   312  	list, err := ListGameServersByGameServerSetOwner(c.gameServerLister, gsSet)
   313  	if err != nil {
   314  		return err
   315  	}
   316  
   317  	list = c.stateCache.forGameServerSet(gsSet).reconcileWithUpdatedServerList(list)
   318  
   319  	numServersToAdd, toDelete, isPartial := computeReconciliationAction(gsSet.Spec.Scheduling, list, c.counter.Counts(),
   320  		int(gsSet.Spec.Replicas), c.maxGameServerCreationsPerBatch, c.maxGameServerDeletionsPerBatch, c.maxPodPendingCount, gsSet.Spec.Priorities)
   321  
   322  	// GameserverSet is marked for deletion then don't add gameservers.
   323  	if !gsSet.DeletionTimestamp.IsZero() {
   324  		numServersToAdd = 0
   325  	}
   326  
   327  	status := computeStatus(gsSet, list)
   328  	fields := logrus.Fields{}
   329  
   330  	for _, gs := range list {
   331  		key := "gsCount" + string(gs.Status.State)
   332  		if gs.ObjectMeta.DeletionTimestamp != nil {
   333  			key += "Deleted"
   334  		}
   335  		v, ok := fields[key]
   336  		if !ok {
   337  			v = 0
   338  		}
   339  
   340  		fields[key] = v.(int) + 1
   341  	}
   342  	loggerForGameServerSet(c.baseLogger, gsSet).
   343  		WithField("targetReplicaCount", gsSet.Spec.Replicas).
   344  		WithField("numServersToAdd", numServersToAdd).
   345  		WithField("numServersToDelete", len(toDelete)).
   346  		WithField("isPartial", isPartial).
   347  		WithField("status", status).
   348  		WithFields(fields).
   349  		Debug("Reconciling GameServerSet")
   350  	if isPartial {
   351  		// we've determined that there's work to do, but we've decided not to do all the work in one shot
   352  		// make sure we get a follow-up, by re-scheduling this GSS in the worker queue immediately before this
   353  		// function returns
   354  		defer c.workerqueue.EnqueueImmediately(gsSet)
   355  	}
   356  
   357  	if numServersToAdd > 0 {
   358  		if err := c.addMoreGameServers(ctx, gsSet, numServersToAdd); err != nil {
   359  			loggerForGameServerSet(c.baseLogger, gsSet).WithError(err).Warning("error adding game servers")
   360  			return errors.Wrap(err, "error adding game servers")
   361  		}
   362  	}
   363  
   364  	if len(toDelete) > 0 {
   365  		if err := c.deleteGameServers(ctx, gsSet, toDelete); err != nil {
   366  			loggerForGameServerSet(c.baseLogger, gsSet).WithError(err).Warning("error deleting game servers")
   367  			return errors.Wrap(err, "error deleting game servers")
   368  		}
   369  	}
   370  
   371  	return c.syncGameServerSetStatus(ctx, gsSet, list)
   372  }
   373  
   374  // computeReconciliationAction computes the action to take to reconcile a game server set set given
   375  // the list of game servers that were found and target replica count.
   376  func computeReconciliationAction(strategy apis.SchedulingStrategy, list []*agonesv1.GameServer,
   377  	counts map[string]gameservers.NodeCount, targetReplicaCount int, maxCreations int, maxDeletions int,
   378  	maxPending int, priorities []agonesv1.Priority) (int, []*agonesv1.GameServer, bool) {
   379  	var upCount int     // up == Ready or will become ready
   380  	var deleteCount int // number of gameservers to delete
   381  
   382  	// track the number of pods that are being created at any given moment by the GameServerSet
   383  	// so we can limit it at a throughput that Kubernetes can handle
   384  	var podPendingCount int // podPending == "up" but don't have a Pod running yet
   385  
   386  	var potentialDeletions []*agonesv1.GameServer
   387  	var toDelete []*agonesv1.GameServer
   388  
   389  	scheduleDeletion := func(gs *agonesv1.GameServer) {
   390  		toDelete = append(toDelete, gs)
   391  		deleteCount--
   392  	}
   393  
   394  	handleGameServerUp := func(gs *agonesv1.GameServer) {
   395  		if upCount >= targetReplicaCount {
   396  			deleteCount++
   397  		} else {
   398  			upCount++
   399  		}
   400  
   401  		// Track gameservers that could be potentially deleted
   402  		potentialDeletions = append(potentialDeletions, gs)
   403  	}
   404  
   405  	// pass 1 - count allocated/reserved servers only, since those can't be touched
   406  	for _, gs := range list {
   407  		if !gs.IsDeletable() {
   408  			upCount++
   409  		}
   410  	}
   411  
   412  	// pass 2 - handle all other statuses
   413  	for _, gs := range list {
   414  		if !gs.IsDeletable() {
   415  			// already handled above
   416  			continue
   417  		}
   418  
   419  		// GS being deleted don't count.
   420  		if gs.IsBeingDeleted() {
   421  			continue
   422  		}
   423  
   424  		switch gs.Status.State {
   425  		case agonesv1.GameServerStatePortAllocation:
   426  			podPendingCount++
   427  			handleGameServerUp(gs)
   428  		case agonesv1.GameServerStateCreating:
   429  			podPendingCount++
   430  			handleGameServerUp(gs)
   431  		case agonesv1.GameServerStateStarting:
   432  			podPendingCount++
   433  			handleGameServerUp(gs)
   434  		case agonesv1.GameServerStateScheduled:
   435  			podPendingCount++
   436  			handleGameServerUp(gs)
   437  		case agonesv1.GameServerStateRequestReady:
   438  			handleGameServerUp(gs)
   439  		case agonesv1.GameServerStateReady:
   440  			handleGameServerUp(gs)
   441  		case agonesv1.GameServerStateReserved:
   442  			handleGameServerUp(gs)
   443  
   444  		// GameServerStateShutdown - already handled above
   445  		// GameServerStateAllocated - already handled above
   446  		case agonesv1.GameServerStateError:
   447  			if !shouldDeleteErroredGameServer(gs) {
   448  				// The GameServer is in an Error state and should not be deleted yet.
   449  				// To stop an ever-increasing number of GameServers from being created,
   450  				// consider the Error state GameServers as up and pending. This stops high
   451  				// churn rate that can negatively impact Kubernetes.
   452  				podPendingCount++
   453  				handleGameServerUp(gs)
   454  			} else {
   455  				scheduleDeletion(gs)
   456  			}
   457  
   458  		case agonesv1.GameServerStateUnhealthy:
   459  			scheduleDeletion(gs)
   460  		default:
   461  			// unrecognized state, assume it's up.
   462  			handleGameServerUp(gs)
   463  		}
   464  	}
   465  
   466  	var partialReconciliation bool
   467  	var numServersToAdd int
   468  
   469  	if upCount < targetReplicaCount {
   470  		numServersToAdd = targetReplicaCount - upCount
   471  		originalNumServersToAdd := numServersToAdd
   472  
   473  		if numServersToAdd > maxCreations {
   474  			numServersToAdd = maxCreations
   475  		}
   476  
   477  		if numServersToAdd+podPendingCount > maxPending {
   478  			numServersToAdd = maxPending - podPendingCount
   479  			if numServersToAdd < 0 {
   480  				numServersToAdd = 0
   481  			}
   482  		}
   483  
   484  		if originalNumServersToAdd != numServersToAdd {
   485  			partialReconciliation = true
   486  		}
   487  	}
   488  
   489  	if deleteCount > 0 {
   490  		potentialDeletions = SortGameServersByStrategy(strategy, potentialDeletions, counts, priorities)
   491  		toDelete = append(toDelete, potentialDeletions[0:deleteCount]...)
   492  	}
   493  
   494  	if len(toDelete) > maxDeletions {
   495  		toDelete = toDelete[0:maxDeletions]
   496  		partialReconciliation = true
   497  	}
   498  
   499  	return numServersToAdd, toDelete, partialReconciliation
   500  }
   501  
   502  func shouldDeleteErroredGameServer(gs *agonesv1.GameServer) bool {
   503  	erroredAtStr := gs.Annotations[agonesv1.GameServerErroredAtAnnotation]
   504  	if erroredAtStr == "" {
   505  		return true
   506  	}
   507  
   508  	erroredAt, err := time.Parse(time.RFC3339, erroredAtStr)
   509  	if err != nil {
   510  		// The annotation is in the wrong format, delete the GameServer.
   511  		return true
   512  	}
   513  
   514  	if time.Since(erroredAt) >= gameServerErrorDeletionDelay {
   515  		return true
   516  	}
   517  	return false
   518  }
   519  
   520  // addMoreGameServers adds diff more GameServers to the set
   521  func (c *Controller) addMoreGameServers(ctx context.Context, gsSet *agonesv1.GameServerSet, count int) (err error) {
   522  	loggerForGameServerSet(c.baseLogger, gsSet).WithField("count", count).Debug("Adding more gameservers")
   523  	latency := c.newMetrics(ctx)
   524  	latency.setRequest(count)
   525  
   526  	defer func() {
   527  		if err != nil {
   528  			latency.setError("error")
   529  		}
   530  		latency.record()
   531  
   532  	}()
   533  
   534  	return parallelize(newGameServersChannel(count, gsSet), c.maxCreationParallelism, func(gs *agonesv1.GameServer) error {
   535  		gs, err := c.gameServerGetter.GameServers(gs.Namespace).Create(ctx, gs, metav1.CreateOptions{})
   536  		if err != nil {
   537  			return errors.Wrapf(err, "error creating gameserver for gameserverset %s", gsSet.ObjectMeta.Name)
   538  		}
   539  
   540  		c.stateCache.forGameServerSet(gsSet).created(gs)
   541  		c.recorder.Eventf(gsSet, corev1.EventTypeNormal, "SuccessfulCreate", "Created gameserver: %s", gs.ObjectMeta.Name)
   542  		return nil
   543  	})
   544  }
   545  
   546  func (c *Controller) deleteGameServers(ctx context.Context, gsSet *agonesv1.GameServerSet, toDelete []*agonesv1.GameServer) error {
   547  	loggerForGameServerSet(c.baseLogger, gsSet).WithField("diff", len(toDelete)).Debug("Deleting gameservers")
   548  
   549  	return parallelize(gameServerListToChannel(toDelete), c.maxDeletionParallelism, func(gs *agonesv1.GameServer) error {
   550  		// We should not delete the gameservers directly buy set their state to shutdown and let the gameserver controller to delete
   551  		gsCopy := gs.DeepCopy()
   552  		gsCopy.Status.State = agonesv1.GameServerStateShutdown
   553  		_, err := c.gameServerGetter.GameServers(gs.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{})
   554  		if err != nil {
   555  			return errors.Wrapf(err, "error updating gameserver %s from status %s to Shutdown status", gs.ObjectMeta.Name, gs.Status.State)
   556  		}
   557  
   558  		c.stateCache.forGameServerSet(gsSet).deleted(gs)
   559  		c.recorder.Eventf(gsSet, corev1.EventTypeNormal, "SuccessfulDelete", "Deleted gameserver in state %s: %v", gs.Status.State, gs.ObjectMeta.Name)
   560  		return nil
   561  	})
   562  }
   563  
   564  func newGameServersChannel(n int, gsSet *agonesv1.GameServerSet) chan *agonesv1.GameServer {
   565  	gameServers := make(chan *agonesv1.GameServer)
   566  	go func() {
   567  		defer close(gameServers)
   568  
   569  		for i := 0; i < n; i++ {
   570  			gameServers <- gsSet.GameServer()
   571  		}
   572  	}()
   573  
   574  	return gameServers
   575  }
   576  
   577  func gameServerListToChannel(list []*agonesv1.GameServer) chan *agonesv1.GameServer {
   578  	gameServers := make(chan *agonesv1.GameServer)
   579  	go func() {
   580  		defer close(gameServers)
   581  
   582  		for _, gs := range list {
   583  			gameServers <- gs
   584  		}
   585  	}()
   586  
   587  	return gameServers
   588  }
   589  
   590  // parallelize processes a channel of game server objects, invoking the provided callback for items in the channel with the specified degree of parallelism up to a limit.
   591  // Returns nil if all callbacks returned nil or one of the error responses, not necessarily the first one.
   592  func parallelize(gameServers chan *agonesv1.GameServer, parallelism int, work func(gs *agonesv1.GameServer) error) error {
   593  	errch := make(chan error, parallelism)
   594  
   595  	var wg sync.WaitGroup
   596  
   597  	for i := 0; i < parallelism; i++ {
   598  		wg.Add(1)
   599  
   600  		go func() {
   601  			defer wg.Done()
   602  			for it := range gameServers {
   603  				err := work(it)
   604  				if err != nil {
   605  					errch <- err
   606  					break
   607  				}
   608  			}
   609  		}()
   610  	}
   611  	wg.Wait()
   612  	close(errch)
   613  
   614  	for range gameServers {
   615  		// drain any remaining game servers in the channel, in case we did not consume them all
   616  		continue
   617  	}
   618  
   619  	// return first error from the channel, or nil if all successful.
   620  	return <-errch
   621  }
   622  
   623  // syncGameServerSetStatus synchronises the GameServerSet State with active GameServer counts
   624  func (c *Controller) syncGameServerSetStatus(ctx context.Context, gsSet *agonesv1.GameServerSet, list []*agonesv1.GameServer) error {
   625  	return c.updateStatusIfChanged(ctx, gsSet, computeStatus(gsSet, list))
   626  }
   627  
   628  // updateStatusIfChanged updates GameServerSet status if it's different than provided.
   629  func (c *Controller) updateStatusIfChanged(ctx context.Context, gsSet *agonesv1.GameServerSet, status agonesv1.GameServerSetStatus) error {
   630  	if !cmp.Equal(gsSet.Status, status) {
   631  		gsSetCopy := gsSet.DeepCopy()
   632  		gsSetCopy.Status = status
   633  		_, err := c.gameServerSetGetter.GameServerSets(gsSet.ObjectMeta.Namespace).UpdateStatus(ctx, gsSetCopy, metav1.UpdateOptions{})
   634  		if err != nil {
   635  			return errors.Wrapf(err, "error updating status on GameServerSet %s", gsSet.ObjectMeta.Name)
   636  		}
   637  	}
   638  	return nil
   639  }
   640  
   641  // computeStatus computes the status of the game server set.
   642  func computeStatus(gsSet *agonesv1.GameServerSet, list []*agonesv1.GameServer) agonesv1.GameServerSetStatus {
   643  	var status agonesv1.GameServerSetStatus
   644  
   645  	// Initialize list status with empty lists from spec
   646  	if runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
   647  		status.Lists = createInitialListStatus(gsSet)
   648  		status.Counters = createInitialCounterStatus(gsSet)
   649  	}
   650  	for _, gs := range list {
   651  		if gs.IsBeingDeleted() {
   652  			// don't count GS that are being deleted
   653  			status.ShutdownReplicas++
   654  			continue
   655  		}
   656  
   657  		status.Replicas++
   658  		switch gs.Status.State {
   659  		case agonesv1.GameServerStateReady:
   660  			status.ReadyReplicas++
   661  		case agonesv1.GameServerStateAllocated:
   662  			status.AllocatedReplicas++
   663  		case agonesv1.GameServerStateReserved:
   664  			status.ReservedReplicas++
   665  		}
   666  
   667  		// Drop Counters and Lists status if the feature flag has been set to false
   668  		if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
   669  			if len(status.Counters) != 0 || len(status.Lists) != 0 {
   670  				status.Counters = map[string]agonesv1.AggregatedCounterStatus{}
   671  				status.Lists = map[string]agonesv1.AggregatedListStatus{}
   672  			}
   673  		}
   674  		// Aggregates all Counters and Lists only for GameServer all states (except IsBeingDeleted)
   675  		if runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
   676  			status.Counters = aggregateCounters(status.Counters, gs.Status.Counters, gs.Status.State)
   677  			status.Lists = aggregateLists(status.Lists, gs.Status.Lists, gs.Status.State)
   678  		}
   679  	}
   680  
   681  	if runtime.FeatureEnabled(runtime.FeaturePlayerTracking) {
   682  		// to make this code simpler, while the feature gate is in place,
   683  		// we will loop around the gs list twice.
   684  		status.Players = &agonesv1.AggregatedPlayerStatus{}
   685  		// TODO: integrate this extra loop into the above for loop when PlayerTracking moves to GA
   686  		for _, gs := range list {
   687  			if gs.ObjectMeta.DeletionTimestamp.IsZero() &&
   688  				(gs.Status.State == agonesv1.GameServerStateReady ||
   689  					gs.Status.State == agonesv1.GameServerStateReserved ||
   690  					gs.Status.State == agonesv1.GameServerStateAllocated) {
   691  				if gs.Status.Players != nil {
   692  					status.Players.Capacity += gs.Status.Players.Capacity
   693  					status.Players.Count += gs.Status.Players.Count
   694  				}
   695  			}
   696  		}
   697  	}
   698  
   699  	return status
   700  }
   701  
   702  func createInitialListStatus(gsSet *agonesv1.GameServerSet) map[string]agonesv1.AggregatedListStatus {
   703  	list := make(map[string]agonesv1.AggregatedListStatus)
   704  	for name := range gsSet.Spec.Template.Spec.Lists {
   705  		list[name] = agonesv1.AggregatedListStatus{}
   706  	}
   707  	return list
   708  }
   709  
   710  func createInitialCounterStatus(gsSet *agonesv1.GameServerSet) map[string]agonesv1.AggregatedCounterStatus {
   711  	counters := make(map[string]agonesv1.AggregatedCounterStatus)
   712  	for name := range gsSet.Spec.Template.Spec.Counters {
   713  		counters[name] = agonesv1.AggregatedCounterStatus{}
   714  	}
   715  	return counters
   716  }
   717  
   718  // aggregateCounters adds the contents of a CounterStatus map to an AggregatedCounterStatus map.
   719  func aggregateCounters(aggCounterStatus map[string]agonesv1.AggregatedCounterStatus,
   720  	counterStatus map[string]agonesv1.CounterStatus,
   721  	gsState agonesv1.GameServerState) map[string]agonesv1.AggregatedCounterStatus {
   722  
   723  	if aggCounterStatus == nil {
   724  		aggCounterStatus = make(map[string]agonesv1.AggregatedCounterStatus)
   725  	}
   726  
   727  	for key, val := range counterStatus {
   728  		// If the Counter exists in both maps, aggregate the values.
   729  		if counter, ok := aggCounterStatus[key]; ok {
   730  			// Aggregate for all game server statuses (expected IsBeingDeleted)
   731  			counter.Count = agonesv1.SafeAdd(counter.Count, val.Count)
   732  			counter.Capacity = agonesv1.SafeAdd(counter.Capacity, val.Capacity)
   733  
   734  			// Aggregate for Allocated game servers only
   735  			if gsState == agonesv1.GameServerStateAllocated {
   736  				counter.AllocatedCount = agonesv1.SafeAdd(counter.AllocatedCount, val.Count)
   737  				counter.AllocatedCapacity = agonesv1.SafeAdd(counter.AllocatedCapacity, val.Capacity)
   738  			}
   739  			aggCounterStatus[key] = counter
   740  		} else {
   741  			tmp := val.DeepCopy()
   742  			allocatedCount := int64(0)
   743  			allocatedCapacity := int64(0)
   744  			if gsState == agonesv1.GameServerStateAllocated {
   745  				allocatedCount = tmp.Count
   746  				allocatedCapacity = tmp.Capacity
   747  			}
   748  			aggCounterStatus[key] = agonesv1.AggregatedCounterStatus{
   749  				AllocatedCount:    allocatedCount,
   750  				AllocatedCapacity: allocatedCapacity,
   751  				Capacity:          tmp.Capacity,
   752  				Count:             tmp.Count,
   753  			}
   754  		}
   755  	}
   756  
   757  	return aggCounterStatus
   758  }
   759  
   760  // aggregateLists adds the contents of a ListStatus map to an AggregatedListStatus map.
   761  func aggregateLists(aggListStatus map[string]agonesv1.AggregatedListStatus,
   762  	listStatus map[string]agonesv1.ListStatus,
   763  	gsState agonesv1.GameServerState) map[string]agonesv1.AggregatedListStatus {
   764  
   765  	if aggListStatus == nil {
   766  		aggListStatus = make(map[string]agonesv1.AggregatedListStatus)
   767  	}
   768  
   769  	for key, val := range listStatus {
   770  		// If the List exists in both maps, aggregate the values.
   771  		if list, ok := aggListStatus[key]; ok {
   772  			list.Capacity += val.Capacity
   773  			// We do include duplicates in the Count.
   774  			list.Count += int64(len(val.Values))
   775  			if gsState == agonesv1.GameServerStateAllocated {
   776  				list.AllocatedCount += int64(len(val.Values))
   777  				list.AllocatedCapacity += val.Capacity
   778  			}
   779  			aggListStatus[key] = list
   780  		} else {
   781  			tmp := val.DeepCopy()
   782  			allocatedCount := int64(0)
   783  			allocatedCapacity := int64(0)
   784  			if gsState == agonesv1.GameServerStateAllocated {
   785  				allocatedCount = int64(len(tmp.Values))
   786  				allocatedCapacity = tmp.Capacity
   787  			}
   788  			aggListStatus[key] = agonesv1.AggregatedListStatus{
   789  				AllocatedCount:    allocatedCount,
   790  				AllocatedCapacity: allocatedCapacity,
   791  				Capacity:          tmp.Capacity,
   792  				Count:             int64(len(tmp.Values)),
   793  			}
   794  		}
   795  	}
   796  
   797  	return aggListStatus
   798  }
   799  
   800  // newMetrics creates a new gss latency recorder.
   801  func (c *Controller) newMetrics(ctx context.Context) *metrics {
   802  	ctx, err := tag.New(ctx, latencyTags...)
   803  	if err != nil {
   804  		c.baseLogger.WithError(err).Warn("failed to tag latency recorder.")
   805  	}
   806  	return &metrics{
   807  		ctx:              ctx,
   808  		gameServerLister: c.gameServerLister,
   809  		logger:           c.baseLogger,
   810  		start:            time.Now(),
   811  	}
   812  }