func (r *GameServerBuildReconciler) gameServersUnderCreationWereCreated(ctx context.Context, gsb *mpsv1alpha1.GameServerBuild) (bool, error) {
	if val, exists := gameServersUnderCreation.Get(gsb.Name); exists {
		gameServersUnderCreationForBuild := val.(map[string]interface{})
		for k := range gameServersUnderCreationForBuild {
			var g mpsv1alpha1.GameServer
			if err := r.Get(ctx, types.NamespacedName{Name: k, Namespace: gsb.Namespace}, &g); err != nil {
				if apierrors.IsNotFound(err) {
					return false, nil
				}
				return false, err
			}
			delete(gameServersUnderCreationForBuild, k)
		}
		if len(gameServersUnderCreationForBuild) == 0 {
			gameServersUnderCreation.Del(gsb.Name)
			return true, nil
		} else {
			return false, nil
		}
	}
	return true, nil
}
gameServersUnderCreationWereCreated checks if all GameServers under creation exist in cache
returns true if all GameServers under creation exist in cache
false otherwise
package controllers
import (
	"context"
	"fmt"
	"time"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/record"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller"
	"sigs.k8s.io/controller-runtime/pkg/log"
	mpsv1alpha1 "github.com/playfab/thundernetes/operator/api/v1alpha1"
	hm "github.com/cornelk/hashmap"
)
var gameServersUnderCreation = &hm.HashMap{}
var gameServersUnderDeletion = &hm.HashMap{}
type GameServerBuildReconciler struct {
	client.Client
	Scheme       *runtime.Scheme
	PortRegistry *PortRegistry
	Recorder     record.EventRecorder
}
func (r *GameServerBuildReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := log.FromContext(ctx)
	var gsb mpsv1alpha1.GameServerBuild
	if err := r.Get(ctx, req.NamespacedName, &gsb); err != nil {
		if apierrors.IsNotFound(err) {
			log.Info("Unable to fetch GameServerBuild - skipping")
			return ctrl.Result{}, nil
		}
		log.Error(err, "unable to fetch gameServerBuild")
		return ctrl.Result{}, err
	}
	if gsb.Status.Health == mpsv1alpha1.BuildUnhealthy {
		log.Info("GameServerBuild is unhealthy, do nothing")
		r.Recorder.Event(&gsb, corev1.EventTypeNormal, "Unhealthy Build", "GameServerBuild is unhealthy, do nothing")
		return ctrl.Result{}, nil
	}
	deletionsCompleted, err := r.gameServersUnderDeletionWereDeleted(ctx, &gsb)
	if err != nil {
		return ctrl.Result{}, err
	}
	if !deletionsCompleted {
		return ctrl.Result{}, nil
	}
	creationsCompleted, err := r.gameServersUnderCreationWereCreated(ctx, &gsb)
	if err != nil {
		return ctrl.Result{}, err
	}
	if !creationsCompleted {
		return ctrl.Result{}, nil
	}
	var gameServers mpsv1alpha1.GameServerList
	if err := r.List(ctx, &gameServers, client.InNamespace(req.Namespace), client.MatchingFields{ownerKey: req.Name}); err != nil {
		return ctrl.Result{}, err
	}
	var activeCount, standingByCount, crashesCount, initializingCount int
	for i := 0; i < len(gameServers.Items); i++ {
		gs := gameServers.Items[i]
		if gs.Status.State == "" {
			initializingCount++
		} else if gs.Status.State == mpsv1alpha1.GameServerStateStandingBy {
			standingByCount++
		} else if gs.Status.State == mpsv1alpha1.GameServerStateActive {
			activeCount++
		} else if gs.Status.State == mpsv1alpha1.GameServerStateCrashed {
			crashesCount++
			if err := r.Delete(ctx, &gs); err != nil {
				return ctrl.Result{}, err
			}
			GameServersSessionEndedCounter.WithLabelValues(gsb.Name).Inc()
			addGameServerToUnderDeletionMap(gsb.Name, gs.Name)
			r.Recorder.Eventf(&gsb, corev1.EventTypeNormal, "Crashed", "GameServer %s crashed", gs.Name)
		} else if gs.Status.State == mpsv1alpha1.GameServerStateGameCompleted {
			if err := r.Delete(ctx, &gs); err != nil {
				return ctrl.Result{}, err
			}
			GameServersCrashedCounter.WithLabelValues(gsb.Name).Inc()
			addGameServerToUnderDeletionMap(gsb.Name, gs.Name)
			r.Recorder.Eventf(&gsb, corev1.EventTypeNormal, "Exited", "GameServer %s session completed", gs.Name)
		}
	}
	if initializingCount > 0 {
		return r.updateStatus(ctx, &gsb, initializingCount, standingByCount, activeCount, crashesCount)
	}
	if standingByCount > gsb.Spec.StandingBy {
		deletedCount := 0
		for i := 0; i < standingByCount-gsb.Spec.StandingBy; i++ {
			gs := gameServers.Items[i]
			if gs.Status.State == "" || gs.Status.State == mpsv1alpha1.GameServerStateStandingBy {
				if err := r.Delete(ctx, &gs); err != nil {
					return ctrl.Result{}, err
				}
				GameServersDeletedCounter.WithLabelValues(gsb.Name).Inc()
				addGameServerToUnderDeletionMap(gsb.Name, gs.Name)
				deletedCount++
				r.Recorder.Eventf(&gsb, corev1.EventTypeNormal, "GameServer deleted", "GameServer %s deleted", gs.Name)
			}
		}
		if deletedCount != standingByCount-gsb.Spec.StandingBy {
			log.Info("User modified .Spec.StandingBy - No standingBy servers left to delete")
			r.Recorder.Eventf(&gsb, corev1.EventTypeNormal, "User modified .Spec.StandingBy - No standingBy servers left to delete", "Tried to delete %d GameServers but deleted only %d", standingByCount-gsb.Spec.StandingBy, deletedCount)
		}
	}
	if standingByCount+activeCount > gsb.Spec.Max {
		deletedCount := 0
		for i := 0; i <= standingByCount+activeCount-gsb.Spec.Max; i++ {
			gs := gameServers.Items[i]
			if gs.Status.State == "" || gs.Status.State == mpsv1alpha1.GameServerStateStandingBy {
				if err := r.Delete(ctx, &gs); err != nil {
					return ctrl.Result{}, err
				}
				GameServersDeletedCounter.WithLabelValues(gsb.Name).Inc()
				addGameServerToUnderDeletionMap(gsb.Name, gs.Name)
				deletedCount++
			}
		}
		if deletedCount != standingByCount+activeCount-gsb.Spec.Max {
			log.Info("User modified .Spec.Max - No standingBy servers left to delete")
			r.Recorder.Eventf(&gsb, corev1.EventTypeNormal, "User modified .Spec.Max - No standingBy servers left to delete. Will requeue", "Tried to delete %d GameServers but deleted only %d", standingByCount+activeCount-gsb.Spec.Max, deletedCount)
			return ctrl.Result{RequeueAfter: time.Duration(5) * time.Second}, nil
		}
	}
	for i := 0; i < gsb.Spec.StandingBy-standingByCount && i+standingByCount+activeCount < gsb.Spec.Max; i++ {
		newgs, err := NewGameServerForGameServerBuild(&gsb, r.PortRegistry)
		if err != nil {
			return ctrl.Result{}, err
		}
		if err := r.Create(ctx, newgs); err != nil {
			return ctrl.Result{}, err
		}
		addGameServerToUnderCreationMap(gsb.Name, newgs.Name)
		GameServersCreatedCounter.WithLabelValues(gsb.Name).Inc()
		r.Recorder.Eventf(&gsb, corev1.EventTypeNormal, "Creating", "Creating GameServer %s", newgs.Name)
	}
	return r.updateStatus(ctx, &gsb, initializingCount, standingByCount, activeCount, crashesCount)
}
func (r *GameServerBuildReconciler) updateStatus(ctx context.Context, gsb *mpsv1alpha1.GameServerBuild, initializingCount, standingByCount, activeCount, crashesCount int) (ctrl.Result, error) {
	if gsb.Status.CurrentInitializing != initializingCount ||
		gsb.Status.CurrentActive != activeCount ||
		gsb.Status.CurrentStandingBy != standingByCount ||
		crashesCount > 0 {
		gsb.Status.CurrentInitializing = initializingCount
		gsb.Status.CurrentActive = activeCount
		gsb.Status.CurrentStandingBy = standingByCount
		gsb.Status.CrashesCount = gsb.Status.CrashesCount + crashesCount
		gsb.Status.CurrentStandingByReadyDesired = fmt.Sprintf("%d/%d", standingByCount, gsb.Spec.StandingBy)
		var health mpsv1alpha1.GameServerBuildHealth
		if gsb.Status.CrashesCount >= gsb.Spec.CrashesToMarkUnhealthy {
			health = mpsv1alpha1.BuildUnhealthy
		} else {
			health = mpsv1alpha1.BuildHealthy
		}
		gsb.Status.Health = health
		if err := r.Status().Update(ctx, gsb); err != nil {
			if apierrors.IsConflict(err) {
				return ctrl.Result{Requeue: true}, nil
			} else {
				return ctrl.Result{}, err
			}
		}
	}
	CurrentGameServerGauge.WithLabelValues(gsb.Name, InitializingServerStatus).Set(float64(initializingCount))
	CurrentGameServerGauge.WithLabelValues(gsb.Name, StandingByServerStatus).Set(float64(standingByCount))
	CurrentGameServerGauge.WithLabelValues(gsb.Name, ActiveServerStatus).Set(float64(activeCount))
	return ctrl.Result{}, nil
}
func (r *GameServerBuildReconciler) SetupWithManager(mgr ctrl.Manager) error {
	if err := mgr.GetFieldIndexer().IndexField(context.Background(), &mpsv1alpha1.GameServer{}, ownerKey, func(rawObj client.Object) []string {
		gs := rawObj.(*mpsv1alpha1.GameServer)
		owner := metav1.GetControllerOf(gs)
		if owner == nil {
			return nil
		}
		if owner.APIVersion != apiGVStr || owner.Kind != "GameServerBuild" {
			return nil
		}
		return []string{owner.Name}
	}); err != nil {
		return err
	}
	return ctrl.NewControllerManagedBy(mgr).
		For(&mpsv1alpha1.GameServerBuild{}).
		Owns(&mpsv1alpha1.GameServer{}).
		WithOptions(controller.Options{MaxConcurrentReconciles: 10}).
		Complete(r)
}
func addGameServerToUnderDeletionMap(gameServerBuildName, gameServerName string) {
	val, _ := gameServersUnderDeletion.GetOrInsert(gameServerBuildName, make(map[string]interface{}))
	v := val.(map[string]interface{})
	v[gameServerName] = struct{}{}
}
func addGameServerToUnderCreationMap(gameServerBuildName, gameServerName string) {
	val, _ := gameServersUnderCreation.GetOrInsert(gameServerBuildName, make(map[string]interface{}))
	v := val.(map[string]interface{})
	v[gameServerName] = struct{}{}
}
func (r *GameServerBuildReconciler) gameServersUnderDeletionWereDeleted(ctx context.Context, gsb *mpsv1alpha1.GameServerBuild) (bool, error) {
	if val, exists := gameServersUnderDeletion.Get(gsb.Name); exists {
		gameServersUnderDeletionForBuild := val.(map[string]interface{})
		for k := range gameServersUnderDeletionForBuild {
			var g mpsv1alpha1.GameServer
			if err := r.Get(ctx, types.NamespacedName{Name: k, Namespace: gsb.Namespace}, &g); err != nil {
				if apierrors.IsNotFound(err) {
					delete(gameServersUnderDeletionForBuild, k)
					continue
				}
				return false, err
			}
		}
		if len(gameServersUnderDeletionForBuild) == 0 {
			gameServersUnderDeletion.Del(gsb.Name)
			return true, nil
		} else {
			return false, nil
		}
	}
	return true, nil
}