agones.dev/agones@v1.54.0/pkg/gameserversets/allocation_overflow.go (about) 1 // Copyright 2023 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 "time" 20 21 "agones.dev/agones/pkg/apis/agones" 22 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 23 "agones.dev/agones/pkg/client/clientset/versioned" 24 getterv1 "agones.dev/agones/pkg/client/clientset/versioned/typed/agones/v1" 25 "agones.dev/agones/pkg/client/informers/externalversions" 26 listerv1 "agones.dev/agones/pkg/client/listers/agones/v1" 27 "agones.dev/agones/pkg/gameservers" 28 "agones.dev/agones/pkg/util/logfields" 29 "agones.dev/agones/pkg/util/runtime" 30 "agones.dev/agones/pkg/util/workerqueue" 31 "github.com/heptiolabs/healthcheck" 32 "github.com/pkg/errors" 33 "github.com/sirupsen/logrus" 34 k8serrors "k8s.io/apimachinery/pkg/api/errors" 35 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 36 "k8s.io/client-go/tools/cache" 37 ) 38 39 // AllocationOverflowController watches `GameServerSets`, and those with configured 40 // AllocationOverflow settings, will the relevant labels and annotations to `GameServers` attached to the given 41 // `GameServerSet` 42 type AllocationOverflowController struct { 43 baseLogger *logrus.Entry 44 counter *gameservers.PerNodeCounter 45 gameServerSynced cache.InformerSynced 46 gameServerGetter getterv1.GameServersGetter 47 gameServerLister listerv1.GameServerLister 48 gameServerSetSynced cache.InformerSynced 49 gameServerSetLister listerv1.GameServerSetLister 50 workerqueue *workerqueue.WorkerQueue 51 } 52 53 // NewAllocatorOverflowController returns a new AllocationOverflowController 54 func NewAllocatorOverflowController( 55 health healthcheck.Handler, 56 counter *gameservers.PerNodeCounter, 57 agonesClient versioned.Interface, 58 agonesInformerFactory externalversions.SharedInformerFactory) *AllocationOverflowController { 59 gameServers := agonesInformerFactory.Agones().V1().GameServers() 60 gameServerSet := agonesInformerFactory.Agones().V1().GameServerSets() 61 gsSetInformer := gameServerSet.Informer() 62 63 c := &AllocationOverflowController{ 64 counter: counter, 65 gameServerSynced: gameServers.Informer().HasSynced, 66 gameServerGetter: agonesClient.AgonesV1(), 67 gameServerLister: gameServers.Lister(), 68 gameServerSetSynced: gsSetInformer.HasSynced, 69 gameServerSetLister: gameServerSet.Lister(), 70 } 71 72 c.baseLogger = runtime.NewLoggerWithType(c) 73 c.baseLogger.Debug("Created!") 74 c.workerqueue = workerqueue.NewWorkerQueueWithRateLimiter(c.syncGameServerSet, c.baseLogger, logfields.GameServerSetKey, agones.GroupName+".GameServerSetController", workerqueue.FastRateLimiter(3*time.Second)) 75 health.AddLivenessCheck("gameserverset-allocationoverflow-workerqueue", c.workerqueue.Healthy) 76 77 _, _ = gsSetInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 78 UpdateFunc: func(_, newObj interface{}) { 79 newGss := newObj.(*agonesv1.GameServerSet) 80 81 // Only process if there is an AllocationOverflow, and it has labels or annotations. 82 if newGss.Spec.AllocationOverflow == nil { 83 return 84 } else if len(newGss.Spec.AllocationOverflow.Labels) == 0 && len(newGss.Spec.AllocationOverflow.Annotations) == 0 { 85 return 86 } 87 if newGss.Status.AllocatedReplicas > newGss.Spec.Replicas { 88 c.workerqueue.Enqueue(newGss) 89 } 90 }, 91 }) 92 93 return c 94 } 95 96 // Run this controller. Will block until stop is closed. 97 func (c *AllocationOverflowController) Run(ctx context.Context) error { 98 c.baseLogger.Debug("Wait for cache sync") 99 if !cache.WaitForCacheSync(ctx.Done(), c.gameServerSynced, c.gameServerSetSynced) { 100 return errors.New("failed to wait for caches to sync") 101 } 102 103 c.workerqueue.Run(ctx, 1) 104 return nil 105 } 106 107 // syncGameServerSet checks to see if there are overflow Allocated GameServers and applied the labels and/or 108 // annotations to the requisite number of GameServers needed to alert the underlying system. 109 func (c *AllocationOverflowController) syncGameServerSet(ctx context.Context, key string) error { 110 // Convert the namespace/name string into a distinct namespace and name 111 namespace, name, err := cache.SplitMetaNamespaceKey(key) 112 if err != nil { 113 // don't return an error, as we don't want this retried 114 runtime.HandleError(loggerForGameServerSetKey(c.baseLogger, key), errors.Wrapf(err, "invalid resource key")) 115 return nil 116 } 117 118 gsSet, err := c.gameServerSetLister.GameServerSets(namespace).Get(name) 119 if err != nil { 120 if k8serrors.IsNotFound(err) { 121 loggerForGameServerSetKey(c.baseLogger, key).Debug("GameServerSet is no longer available for syncing") 122 return nil 123 } 124 return errors.Wrapf(err, "error retrieving GameServerSet %s from namespace %s", name, namespace) 125 } 126 127 // just in case something changed, double check to avoid panics and/or sending work to the K8s API that we don't 128 // need to 129 if gsSet.Spec.AllocationOverflow == nil { 130 return nil 131 } 132 if gsSet.Status.AllocatedReplicas <= gsSet.Spec.Replicas { 133 return nil 134 } 135 136 overflow := gsSet.Status.AllocatedReplicas - gsSet.Spec.Replicas 137 138 list, err := ListGameServersByGameServerSetOwner(c.gameServerLister, gsSet) 139 if err != nil { 140 return err 141 } 142 143 matches, rest := gsSet.Spec.AllocationOverflow.CountMatches(list) 144 if matches >= overflow { 145 return nil 146 } 147 148 rest = SortGameServersByStrategy(gsSet.Spec.Scheduling, rest, c.counter.Counts(), gsSet.Spec.Priorities) 149 rest = rest[:(overflow - matches)] 150 151 opts := v1.UpdateOptions{} 152 for _, gs := range rest { 153 gsCopy := gs.DeepCopy() 154 gsSet.Spec.AllocationOverflow.Apply(gsCopy) 155 156 if _, err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(ctx, gsCopy, opts); err != nil { 157 return errors.Wrapf(err, "error updating GameServer %s with overflow labels and/or annotations", gs.ObjectMeta.Name) 158 } 159 } 160 161 return nil 162 }