agones.dev/agones@v1.53.0/pkg/metrics/util_test.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 metrics 16 17 import ( 18 "context" 19 "reflect" 20 "strconv" 21 "testing" 22 23 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 24 autoscalingv1 "agones.dev/agones/pkg/apis/autoscaling/v1" 25 agtesting "agones.dev/agones/pkg/testing" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 v1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/util/intstr" 31 "k8s.io/apimachinery/pkg/util/rand" 32 "k8s.io/apimachinery/pkg/util/uuid" 33 "k8s.io/apimachinery/pkg/watch" 34 k8stesting "k8s.io/client-go/testing" 35 "k8s.io/client-go/tools/cache" 36 ) 37 38 var ( 39 // counter for unique GameServer names 40 counter = 0 41 ) 42 43 // newFakeController returns a controller, backed by the fake Clientset 44 func newFakeController() *fakeController { 45 m := agtesting.NewMocks() 46 return newFakeControllerWithMock(m) 47 } 48 49 // newFakeControllerWithMock returns a Controller with a pre-populated mock. 50 // This is useful if you want to populate a data set before the informer starts. 51 func newFakeControllerWithMock(m agtesting.Mocks) *fakeController { 52 c := NewController(m.KubeClient, m.AgonesClient, m.KubeInformerFactory, m.AgonesInformerFactory) 53 gsWatch := watch.NewFake() 54 fasWatch := watch.NewFake() 55 fleetWatch := watch.NewFake() 56 nodeWatch := watch.NewFake() 57 nsWatch := watch.NewFake() 58 59 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil)) 60 m.AgonesClient.AddWatchReactor("fleetautoscalers", k8stesting.DefaultWatchReactor(fasWatch, nil)) 61 m.AgonesClient.AddWatchReactor("fleets", k8stesting.DefaultWatchReactor(fleetWatch, nil)) 62 m.KubeClient.AddWatchReactor("nodes", k8stesting.DefaultWatchReactor(nodeWatch, nil)) 63 m.KubeClient.AddWatchReactor("namespaces", k8stesting.DefaultWatchReactor(nsWatch, nil)) 64 65 ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced, c.fleetSynced, c.fasSynced) 66 67 return &fakeController{ 68 Controller: c, 69 Mocks: m, 70 gsWatch: gsWatch, 71 fasWatch: fasWatch, 72 fleetWatch: fleetWatch, 73 cancel: cancel, 74 ctx: ctx, 75 } 76 } 77 78 func (c *fakeController) close() { 79 if c.cancel != nil { 80 c.cancel() 81 } 82 } 83 84 func (c *fakeController) run(t *testing.T) { 85 go func() { 86 err := c.Controller.Run(c.ctx, 1) 87 assert.Nil(t, err) 88 }() 89 c.sync() 90 } 91 92 func (c *fakeController) sync() bool { 93 return cache.WaitForCacheSync(c.ctx.Done(), c.gameServerSynced, c.fleetSynced, c.fasSynced) 94 } 95 96 type fakeController struct { 97 *Controller 98 agtesting.Mocks 99 gsWatch *watch.FakeWatcher 100 fasWatch *watch.FakeWatcher 101 fleetWatch *watch.FakeWatcher 102 ctx context.Context 103 cancel context.CancelFunc 104 } 105 106 func nodeWithName(name string) *v1.Node { 107 return &v1.Node{ 108 ObjectMeta: metav1.ObjectMeta{ 109 Name: name, 110 UID: uuid.NewUUID(), 111 }, 112 } 113 } 114 115 func gameServerWithNode(nodeName string) *agonesv1.GameServer { 116 gs := gameServerWithFleetAndState("fleet", agonesv1.GameServerStateReady) 117 gs.Status.NodeName = nodeName 118 return gs 119 } 120 121 func gameServerWithFleetStateCreationTimestamp(fleetName string, gsName string, state agonesv1.GameServerState, t metav1.Time) *agonesv1.GameServer { 122 gs := gameServerWithFleetAndState(fleetName, state) 123 gs.ObjectMeta.CreationTimestamp = t 124 gs.ObjectMeta.Name = gsName 125 return gs 126 } 127 128 func gameServerWithFleetAndState(fleetName string, state agonesv1.GameServerState) *agonesv1.GameServer { 129 lbs := map[string]string{} 130 if fleetName != "" { 131 lbs[agonesv1.FleetNameLabel] = fleetName 132 } 133 // ensure the name is unique each time. 134 counter++ 135 gs := &agonesv1.GameServer{ 136 ObjectMeta: metav1.ObjectMeta{ 137 Name: rand.String(10) + strconv.Itoa(counter), 138 Namespace: "default", 139 UID: uuid.NewUUID(), 140 Labels: lbs, 141 }, 142 Status: agonesv1.GameServerStatus{ 143 State: state, 144 }, 145 } 146 return gs 147 } 148 149 func generateGsEvents(count int, state agonesv1.GameServerState, fleetName string, fakew *watch.FakeWatcher) { 150 for i := 0; i < count; i++ { 151 gs := gameServerWithFleetAndState(fleetName, agonesv1.GameServerState("")) 152 fakew.Add(gs) 153 gsUpdated := gs.DeepCopy() 154 gsUpdated.Status.State = state 155 fakew.Modify(gsUpdated) 156 // make sure we count only one event 157 fakew.Modify(gsUpdated) 158 } 159 } 160 161 func fleet(fleetName string, total, allocated, ready, desired, reserved int32) *agonesv1.Fleet { 162 return &agonesv1.Fleet{ 163 ObjectMeta: metav1.ObjectMeta{ 164 Name: fleetName, 165 Namespace: "default", 166 UID: uuid.NewUUID(), 167 }, 168 Spec: agonesv1.FleetSpec{ 169 Replicas: desired, 170 }, 171 Status: agonesv1.FleetStatus{ 172 ReservedReplicas: reserved, 173 AllocatedReplicas: allocated, 174 ReadyReplicas: ready, 175 Replicas: total, 176 }, 177 } 178 } 179 180 func fleetAutoScaler(fleetName string, fasName string) *autoscalingv1.FleetAutoscaler { 181 return &autoscalingv1.FleetAutoscaler{ 182 ObjectMeta: metav1.ObjectMeta{ 183 Name: fasName, 184 Namespace: "default", 185 UID: uuid.NewUUID(), 186 }, 187 Spec: autoscalingv1.FleetAutoscalerSpec{ 188 FleetName: fleetName, 189 Policy: autoscalingv1.FleetAutoscalerPolicy{ 190 Type: autoscalingv1.BufferPolicyType, 191 Buffer: &autoscalingv1.BufferPolicy{ 192 MaxReplicas: 30, 193 MinReplicas: 10, 194 BufferSize: intstr.FromInt(11), 195 }, 196 }, 197 Sync: &autoscalingv1.FleetAutoscalerSync{ 198 Type: autoscalingv1.FixedIntervalSyncType, 199 FixedInterval: autoscalingv1.FixedIntervalSync{ 200 Seconds: 30, 201 }, 202 }, 203 }, 204 Status: autoscalingv1.FleetAutoscalerStatus{ 205 AbleToScale: true, 206 ScalingLimited: false, 207 CurrentReplicas: 10, 208 DesiredReplicas: 20, 209 LastAppliedPolicy: autoscalingv1.BufferPolicyType, 210 }, 211 } 212 } 213 214 func TestParseLabels(t *testing.T) { 215 cases := []struct { 216 desc string 217 labels string 218 expected map[string]string 219 err string 220 }{ 221 { 222 desc: "Two valid labels, no error", 223 labels: "l1=1,l2=2", 224 expected: map[string]string{"l1": "1", "l2": "2"}, 225 err: "", 226 }, 227 { 228 desc: "Empty input string, empty struct expected", 229 labels: "", 230 expected: map[string]string{}, 231 err: "", 232 }, 233 { 234 desc: "Valid labels, invalid separator, error expected", 235 labels: "l1=1|l2=2", 236 expected: map[string]string{"l1": "1", "l2": "2"}, 237 err: "invalid labels: l1=1|l2=2, expect key=value,key2=value2", 238 }, 239 { 240 desc: "Two valid labels with extra spaces, error expected", 241 labels: " l1=1, l2=2 ", 242 expected: map[string]string{"l1": "1", "l2": "2"}, 243 err: "", 244 }, 245 { 246 desc: "Two invalid labels, error expected", 247 labels: "l1-1,l2-2", 248 expected: map[string]string{"l1": "1", "l2": "2"}, 249 err: "invalid labels: l1-1,l2-2, expect key=value,key2=value2", 250 }, 251 { 252 desc: "Invalid key utf8 string, error expected", 253 labels: "\xF4=1,l2=2", 254 expected: map[string]string{"l1": "1", "l2": "2"}, 255 err: "invalid key: \xF4, must be a valid utf-8 string", 256 }, 257 { 258 desc: "Invalid value utf8 string, error expected", 259 labels: "l1=\xF4,l2=2", 260 expected: map[string]string{"l1": "1", "l2": "2"}, 261 err: "invalid value: \xF4, must be a valid utf-8 string", 262 }, 263 { 264 desc: "Empty key, error expected", 265 labels: " =1,l2=2", 266 expected: map[string]string{"l1": "1", "l2": "2"}, 267 err: "invalid key: can not be empty", 268 }, 269 { 270 desc: "Empty value, error expected", 271 labels: "l1= ,l2=2", 272 expected: map[string]string{"l1": "1", "l2": "2"}, 273 err: "invalid value for key l1: can not be empty", 274 }, 275 { 276 desc: "Empty key and value, key err excpected", 277 labels: " = ,l2=2", 278 expected: map[string]string{"l1": "1", "l2": "2"}, 279 err: "invalid key: can not be empty", 280 }, 281 } 282 283 for _, tc := range cases { 284 t.Run(tc.desc, func(t *testing.T) { 285 res, err := parseLabels(tc.labels) 286 287 if tc.err != "" { 288 require.Error(t, err) 289 assert.Equal(t, tc.err, err.Error()) 290 } else { 291 require.NoError(t, err) 292 // retrieve inner map 293 m := reflect.ValueOf(res).Elem().FieldByName("m").MapRange() 294 for m.Next() { 295 val, ok := tc.expected[m.Key().String()] 296 require.True(t, ok) 297 assert.Equal(t, val, m.Value().FieldByName("val").String()) 298 } 299 } 300 }) 301 } 302 }