agones.dev/agones@v1.53.0/pkg/metrics/controller_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 "strings" 20 "testing" 21 "time" 22 23 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 24 agtesting "agones.dev/agones/pkg/testing" 25 "agones.dev/agones/pkg/util/runtime" 26 "github.com/google/go-cmp/cmp" 27 "github.com/pkg/errors" 28 "github.com/sirupsen/logrus" 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 "go.opencensus.io/metric/metricdata" 32 "go.opencensus.io/metric/metricexport" 33 corev1 "k8s.io/api/core/v1" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/labels" 36 k8sruntime "k8s.io/apimachinery/pkg/runtime" 37 "k8s.io/apimachinery/pkg/util/intstr" 38 k8stesting "k8s.io/client-go/testing" 39 ) 40 41 const defaultNs = "default" 42 43 type metricExporter struct { 44 metrics []*metricdata.Metric 45 } 46 47 func (e *metricExporter) ExportMetrics(_ context.Context, metrics []*metricdata.Metric) error { 48 e.metrics = metrics 49 return nil 50 } 51 52 func serialize(args []string) string { 53 return strings.Join(args, "|") 54 } 55 56 type expectedMetricData struct { 57 labels []string 58 val interface{} 59 } 60 61 func assertMetricData(t *testing.T, exporter *metricExporter, metricName string, expected []expectedMetricData) { 62 63 expectedValuesAsMap := make(map[string]expectedMetricData) 64 for _, e := range expected { 65 expectedValuesAsMap[serialize(e.labels)] = e 66 } 67 68 var wantedMetric *metricdata.Metric 69 for _, m := range exporter.metrics { 70 if m.Descriptor.Name == metricName { 71 wantedMetric = m 72 } 73 } 74 require.NotNil(t, wantedMetric, "No metric found with name: %s", metricName) 75 76 assert.Equal(t, len(expectedValuesAsMap), len(expected), "Multiple entries in 'expected' slice have the exact same labels") 77 assert.Equalf(t, len(expectedValuesAsMap), len(wantedMetric.TimeSeries), "number of timeseries does not match under metric: %v", metricName) 78 for _, tsd := range wantedMetric.TimeSeries { 79 actualLabelValues := make([]string, len(tsd.LabelValues)) 80 for i, k := range tsd.LabelValues { 81 actualLabelValues[i] = k.Value 82 } 83 e, ok := expectedValuesAsMap[serialize(actualLabelValues)] 84 assert.True(t, ok, "no TimeSeries found with labels: %v", actualLabelValues) 85 assert.Equal(t, e.labels, actualLabelValues, "label values don't match") 86 assert.Equal(t, 1, len(tsd.Points), "assertMetricDataValues can only handle a single Point in a TimeSeries") 87 assert.Equal(t, e.val, tsd.Points[0].Value, "metric: %s, tags: %v, values don't match; got: %v, want: %v", metricName, tsd.LabelValues, tsd.Points[0].Value, e.val) 88 } 89 } 90 91 func resetMetrics() { 92 unRegisterViews() 93 registerViews() 94 } 95 96 func TestControllerGameServerCount(t *testing.T) { 97 resetMetrics() 98 exporter := &metricExporter{} 99 reader := metricexport.NewReader() 100 101 c := newFakeController() 102 defer c.close() 103 104 c.run(t) 105 require.True(t, c.sync()) 106 107 gs1 := gameServerWithFleetAndState("test-fleet", agonesv1.GameServerStateCreating) 108 c.gsWatch.Add(gs1) 109 require.Eventually(t, func() bool { 110 gs, err := c.gameServerLister.GameServers(gs1.ObjectMeta.Namespace).Get(gs1.ObjectMeta.Name) 111 assert.NoError(t, err) 112 return gs.Status.State == agonesv1.GameServerStateCreating 113 }, 5*time.Second, time.Second) 114 c.collect() 115 116 gs1 = gs1.DeepCopy() 117 gs1.Status.State = agonesv1.GameServerStateReady 118 c.gsWatch.Modify(gs1) 119 require.Eventually(t, func() bool { 120 gs, err := c.gameServerLister.GameServers(gs1.ObjectMeta.Namespace).Get(gs1.ObjectMeta.Name) 121 assert.NoError(t, err) 122 return gs.Status.State == agonesv1.GameServerStateReady 123 }, 5*time.Second, time.Second) 124 c.collect() 125 126 gs1 = gs1.DeepCopy() 127 gs1.Status.State = agonesv1.GameServerStateShutdown 128 c.gsWatch.Modify(gs1) 129 c.gsWatch.Add(gameServerWithFleetAndState("", agonesv1.GameServerStatePortAllocation)) 130 c.gsWatch.Add(gameServerWithFleetAndState("", agonesv1.GameServerStatePortAllocation)) 131 132 require.True(t, c.sync()) 133 // Port allocation is last, so wait for that come to the state we expect 134 require.Eventually(t, func() bool { 135 c.collect() 136 ex := &metricExporter{} 137 reader.ReadAndExport(ex) 138 139 for _, m := range ex.metrics { 140 if m.Descriptor.Name == gameServersCountName { 141 if len(m.TimeSeries) == 4 { 142 return true 143 } 144 logrus.WithField("m", m).Info("Metrics") 145 return false 146 } 147 } 148 149 return false 150 }, 10*time.Second, time.Second) 151 152 reader.ReadAndExport(exporter) 153 assertMetricData(t, exporter, gameServersCountName, []expectedMetricData{ 154 {labels: []string{"test-fleet", defaultNs, "Creating"}, val: int64(0)}, 155 {labels: []string{"test-fleet", defaultNs, "Ready"}, val: int64(0)}, 156 {labels: []string{"test-fleet", defaultNs, "Shutdown"}, val: int64(1)}, 157 {labels: []string{"none", defaultNs, "PortAllocation"}, val: int64(2)}, 158 }) 159 } 160 161 func TestControllerGameServerPlayerConnectedCount(t *testing.T) { 162 runtime.FeatureTestMutex.Lock() 163 defer runtime.FeatureTestMutex.Unlock() 164 runtime.EnableAllFeatures() 165 resetMetrics() 166 exporter := &metricExporter{} 167 reader := metricexport.NewReader() 168 169 c := newFakeController() 170 defer c.close() 171 172 gs1 := gameServerWithFleetAndState("test-fleet", agonesv1.GameServerStateReady) 173 gs1.Status.Players = &agonesv1.PlayerStatus{ 174 Count: 0, 175 } 176 c.gsWatch.Add(gs1) 177 gs1 = gs1.DeepCopy() 178 gs1.Status.Players.Count = 1 179 c.gsWatch.Modify(gs1) 180 181 c.run(t) 182 require.True(t, c.sync()) 183 require.Eventually(t, func() bool { 184 gs, err := c.gameServerLister.GameServers(gs1.ObjectMeta.Namespace).Get(gs1.ObjectMeta.Name) 185 assert.NoError(t, err) 186 return gs.Status.Players.Count == 1 187 }, 5*time.Second, time.Second) 188 c.collect() 189 190 gs1 = gs1.DeepCopy() 191 gs1.Status.Players.Count = 4 192 c.gsWatch.Modify(gs1) 193 194 c.run(t) 195 require.True(t, c.sync()) 196 require.Eventually(t, func() bool { 197 gs, err := c.gameServerLister.GameServers(gs1.ObjectMeta.Namespace).Get(gs1.ObjectMeta.Name) 198 assert.NoError(t, err) 199 return gs.Status.Players.Count == 4 200 }, 5*time.Second, time.Second) 201 c.collect() 202 203 reader.ReadAndExport(exporter) 204 assertMetricData(t, exporter, gameServersPlayerConnectedTotalName, []expectedMetricData{ 205 {labels: []string{"test-fleet", gs1.GetName(), defaultNs}, val: int64(4)}, 206 }) 207 } 208 209 func TestControllerGameServerPlayerCapacityCount(t *testing.T) { 210 runtime.FeatureTestMutex.Lock() 211 defer runtime.FeatureTestMutex.Unlock() 212 runtime.EnableAllFeatures() 213 resetMetrics() 214 exporter := &metricExporter{} 215 reader := metricexport.NewReader() 216 217 c := newFakeController() 218 defer c.close() 219 220 gs1 := gameServerWithFleetAndState("test-fleet", agonesv1.GameServerStateReady) 221 gs1.Status.Players = &agonesv1.PlayerStatus{ 222 Capacity: 4, 223 Count: 0, 224 } 225 c.gsWatch.Add(gs1) 226 gs1 = gs1.DeepCopy() 227 gs1.Status.Players.Count = 1 228 c.gsWatch.Modify(gs1) 229 230 c.run(t) 231 require.True(t, c.sync()) 232 require.Eventually(t, func() bool { 233 gs, err := c.gameServerLister.GameServers(gs1.ObjectMeta.Namespace).Get(gs1.ObjectMeta.Name) 234 assert.NoError(t, err) 235 return gs.Status.Players.Count == 1 236 }, 5*time.Second, time.Second) 237 c.collect() 238 239 reader.ReadAndExport(exporter) 240 assertMetricData(t, exporter, gameServersPlayerCapacityTotalName, []expectedMetricData{ 241 {labels: []string{"test-fleet", gs1.GetName(), defaultNs}, val: int64(3)}, 242 }) 243 } 244 245 func TestControllerGameServersTotal(t *testing.T) { 246 resetMetrics() 247 exporter := &metricExporter{} 248 reader := metricexport.NewReader() 249 c := newFakeController() 250 defer c.close() 251 c.run(t) 252 253 // deleted gs should not be counted 254 gs := gameServerWithFleetAndState("deleted", agonesv1.GameServerStateCreating) 255 c.gsWatch.Add(gs) 256 c.gsWatch.Delete(gs) 257 258 generateGsEvents(16, agonesv1.GameServerStateCreating, "test", c.gsWatch) 259 generateGsEvents(15, agonesv1.GameServerStateScheduled, "test", c.gsWatch) 260 generateGsEvents(10, agonesv1.GameServerStateStarting, "test", c.gsWatch) 261 generateGsEvents(1, agonesv1.GameServerStateUnhealthy, "test", c.gsWatch) 262 generateGsEvents(19, agonesv1.GameServerStateCreating, "", c.gsWatch) 263 generateGsEvents(18, agonesv1.GameServerStateScheduled, "", c.gsWatch) 264 generateGsEvents(16, agonesv1.GameServerStateStarting, "", c.gsWatch) 265 generateGsEvents(1, agonesv1.GameServerStateUnhealthy, "", c.gsWatch) 266 267 expected := 96 268 assert.Eventually(t, func() bool { 269 list, err := c.gameServerLister.GameServers(gs.ObjectMeta.Namespace).List(labels.Everything()) 270 require.NoError(t, err) 271 return len(list) == expected 272 }, 5*time.Second, time.Second) 273 // While these values are tested above, the following test checks will provide a more detailed diff output 274 // in the case where the assert.Eventually(...) case fails, which makes failing tests easier to debug. 275 list, err := c.gameServerLister.GameServers(gs.ObjectMeta.Namespace).List(labels.Everything()) 276 require.NoError(t, err) 277 require.Len(t, list, expected) 278 279 reader.ReadAndExport(exporter) 280 assertMetricData(t, exporter, gameServersTotalName, []expectedMetricData{ 281 {labels: []string{"test", defaultNs, "Creating"}, val: int64(16)}, 282 {labels: []string{"test", defaultNs, "Scheduled"}, val: int64(15)}, 283 {labels: []string{"test", defaultNs, "Starting"}, val: int64(10)}, 284 {labels: []string{"test", defaultNs, "Unhealthy"}, val: int64(1)}, 285 {labels: []string{"none", defaultNs, "Creating"}, val: int64(19)}, 286 {labels: []string{"none", defaultNs, "Scheduled"}, val: int64(18)}, 287 {labels: []string{"none", defaultNs, "Starting"}, val: int64(16)}, 288 {labels: []string{"none", defaultNs, "Unhealthy"}, val: int64(1)}, 289 }) 290 } 291 292 func TestControllerFleetOnDeleting(t *testing.T) { 293 294 resetMetrics() 295 exporter := &metricExporter{} 296 reader := metricexport.NewReader() 297 c := newFakeController() 298 defer c.close() 299 c.run(t) 300 301 deletionTime := metav1.NewTime(time.Now()) 302 303 fd := fleet("fleet-deleting", 100, 100, 100, 100, 100) 304 ft := fleet("fleet-test", 8, 2, 5, 1, 1) 305 c.fleetWatch.Add(fd) 306 307 fd = fd.DeepCopy() 308 fd.DeletionTimestamp = &deletionTime 309 c.fleetWatch.Modify(fd) 310 311 c.fleetWatch.Add(ft) 312 ft = ft.DeepCopy() 313 ft.Status.Replicas = 15 314 c.fleetWatch.Modify(ft) 315 316 // wait until the fleet-deleting exists and total value equal 15. 317 require.Eventually(t, func() bool { 318 ex := &metricExporter{} 319 reader.ReadAndExport(ex) 320 321 for _, m := range ex.metrics { 322 if m.Descriptor.Name == fleetReplicaCountName { 323 for _, d := range m.TimeSeries { 324 name := d.LabelValues[0].Value 325 val := d.Points[0].Value 326 if len(name) > 0 && name == "fleet-test" && val == int64(15) { 327 return true 328 } 329 } 330 } 331 } 332 333 return false 334 }, 5*time.Second, time.Second) 335 336 reader.ReadAndExport(exporter) 337 assertMetricData(t, exporter, fleetReplicaCountName, []expectedMetricData{ 338 {labels: []string{"fleet-deleting", defaultNs, "total"}, val: int64(100)}, 339 {labels: []string{"fleet-deleting", defaultNs, "allocated"}, val: int64(100)}, 340 {labels: []string{"fleet-deleting", defaultNs, "ready"}, val: int64(100)}, 341 {labels: []string{"fleet-deleting", defaultNs, "desired"}, val: int64(100)}, 342 {labels: []string{"fleet-deleting", defaultNs, "reserved"}, val: int64(100)}, 343 344 {labels: []string{"fleet-test", defaultNs, "total"}, val: int64(15)}, 345 {labels: []string{"fleet-test", defaultNs, "allocated"}, val: int64(2)}, 346 {labels: []string{"fleet-test", defaultNs, "ready"}, val: int64(5)}, 347 {labels: []string{"fleet-test", defaultNs, "desired"}, val: int64(1)}, 348 {labels: []string{"fleet-test", defaultNs, "reserved"}, val: int64(1)}, 349 }) 350 } 351 352 func TestControllerFleetReplicasCount_ResetMetricsOnDelete(t *testing.T) { 353 runtime.FeatureTestMutex.Lock() 354 defer runtime.FeatureTestMutex.Unlock() 355 356 resetMetrics() 357 exporter := &metricExporter{} 358 reader := metricexport.NewReader() 359 c := newFakeController() 360 defer c.close() 361 c.run(t) 362 363 f := fleet("fleet-test", 8, 2, 5, 1, 1) 364 fd := fleet("fleet-deleted", 100, 100, 100, 100, 100) 365 c.fleetWatch.Add(f) 366 f = f.DeepCopy() 367 f.Status.ReadyReplicas = 1 368 f.Spec.Replicas = 5 369 c.fleetWatch.Modify(f) 370 c.fleetWatch.Add(fd) 371 c.fleetWatch.Delete(fd) 372 373 // wait until the fleet-deleted no longer exists 374 require.Eventually(t, func() bool { 375 ex := &metricExporter{} 376 reader.ReadAndExport(ex) 377 378 for _, m := range ex.metrics { 379 if m.Descriptor.Name == fleetReplicaCountName { 380 for _, d := range m.TimeSeries { 381 value := d.LabelValues[0].Value 382 if len(value) > 0 && value != "fleet-deleted" { 383 return true 384 } 385 } 386 } 387 } 388 389 return false 390 }, 5*time.Second, time.Second) 391 392 reader.ReadAndExport(exporter) 393 assertMetricData(t, exporter, fleetReplicaCountName, []expectedMetricData{ 394 {labels: []string{"fleet-test", defaultNs, "reserved"}, val: int64(1)}, 395 {labels: []string{"fleet-test", defaultNs, "allocated"}, val: int64(2)}, 396 {labels: []string{"fleet-test", defaultNs, "desired"}, val: int64(5)}, 397 {labels: []string{"fleet-test", defaultNs, "ready"}, val: int64(1)}, 398 {labels: []string{"fleet-test", defaultNs, "total"}, val: int64(8)}, 399 }) 400 } 401 402 func TestControllerFleetAutoScalerOnDeleting(t *testing.T) { 403 404 resetMetrics() 405 exporter := &metricExporter{} 406 reader := metricexport.NewReader() 407 c := newFakeController() 408 defer c.close() 409 c.run(t) 410 411 deletionTime := metav1.NewTime(time.Now()) 412 413 fas := fleetAutoScaler("fleet-deleting", "fas-deleting") 414 fast := fleetAutoScaler("fleet-test", "fas-test") 415 c.fasWatch.Add(fas) 416 417 fas = fas.DeepCopy() 418 fas.Status.CurrentReplicas = 15 419 c.fasWatch.Modify(fas) 420 fas = fas.DeepCopy() 421 fas.DeletionTimestamp = &deletionTime 422 c.fasWatch.Modify(fas) 423 424 c.fasWatch.Add(fast) 425 426 fast = fast.DeepCopy() 427 fast.Status.CurrentReplicas = 5 428 c.fasWatch.Modify(fast) 429 430 // wait until the fas-test exists and current-replicas's value euqal 5. 431 require.Eventually(t, func() bool { 432 ex := &metricExporter{} 433 reader.ReadAndExport(ex) 434 435 for _, m := range ex.metrics { 436 if m.Descriptor.Name == fleetAutoscalerCurrentReplicaCountName { 437 for _, d := range m.TimeSeries { 438 name := d.LabelValues[1].Value 439 val := d.Points[0].Value 440 if len(name) > 0 && name == "fas-test" && val == int64(5) { 441 return true 442 } 443 } 444 } 445 } 446 447 return false 448 }, 5*time.Second, time.Second) 449 450 reader.ReadAndExport(exporter) 451 assertMetricData(t, exporter, fleetAutoscalerCurrentReplicaCountName, []expectedMetricData{ 452 {labels: []string{"fleet-deleting", "fas-deleting", defaultNs}, val: int64(15)}, 453 {labels: []string{"fleet-test", "fas-test", defaultNs}, val: int64(5)}, 454 }) 455 } 456 457 func TestControllerFleetAutoScalerState_ResetMetricsOnDelete(t *testing.T) { 458 runtime.FeatureTestMutex.Lock() 459 defer runtime.FeatureTestMutex.Unlock() 460 461 resetMetrics() 462 exporter := &metricExporter{} 463 reader := metricexport.NewReader() 464 c := newFakeController() 465 defer c.close() 466 c.run(t) 467 468 // testing fleet name change 469 fasFleetNameChange := fleetAutoScaler("first-fleet", "name-switch") 470 c.fasWatch.Add(fasFleetNameChange) 471 fasFleetNameChange = fasFleetNameChange.DeepCopy() 472 fasFleetNameChange.Spec.Policy.Buffer.BufferSize = intstr.FromInt(10) 473 fasFleetNameChange.Spec.Policy.Buffer.MaxReplicas = 50 474 fasFleetNameChange.Spec.Policy.Buffer.MinReplicas = 10 475 fasFleetNameChange.Status.CurrentReplicas = 20 476 fasFleetNameChange.Status.DesiredReplicas = 10 477 fasFleetNameChange.Status.ScalingLimited = true 478 c.fasWatch.Modify(fasFleetNameChange) 479 fasFleetNameChange = fasFleetNameChange.DeepCopy() 480 fasFleetNameChange.Spec.FleetName = "second-fleet" 481 c.fasWatch.Modify(fasFleetNameChange) 482 // testing deletion 483 fasDeleted := fleetAutoScaler("deleted-fleet", "deleted") 484 fasDeleted.Spec.Policy.Buffer.BufferSize = intstr.FromString("50%") 485 fasDeleted.Spec.Policy.Buffer.MaxReplicas = 150 486 fasDeleted.Spec.Policy.Buffer.MinReplicas = 15 487 c.fasWatch.Add(fasDeleted) 488 c.fasWatch.Delete(fasDeleted) 489 490 c.sync() 491 // wait until the fleet-deleted no longer exists 492 require.Eventually(t, func() bool { 493 ex := &metricExporter{} 494 reader.ReadAndExport(ex) 495 496 for _, m := range ex.metrics { 497 if m.Descriptor.Name == fleetAutoscalersLimitedName { 498 for _, d := range m.TimeSeries { 499 values := d.LabelValues 500 if len(values[0].Value) > 0 && values[0].Value != "deleted-fleet" && values[1].Value != "deleted" && values[2].Value == defaultNs { 501 return true 502 } 503 } 504 } 505 } 506 507 return false 508 }, 5*time.Second, time.Second) 509 510 reader.ReadAndExport(exporter) 511 assertMetricData(t, exporter, fleetAutoscalersAbleToScaleName, []expectedMetricData{ 512 {labels: []string{"second-fleet", "name-switch", defaultNs}, val: int64(1)}, 513 }) 514 assertMetricData(t, exporter, fleetAutoscalerBufferLimitName, []expectedMetricData{ 515 {labels: []string{"second-fleet", "name-switch", defaultNs, "max"}, val: int64(50)}, 516 {labels: []string{"second-fleet", "name-switch", defaultNs, "min"}, val: int64(10)}, 517 }) 518 assertMetricData(t, exporter, fleetAutoscalterBufferSizeName, []expectedMetricData{ 519 {labels: []string{"second-fleet", "name-switch", defaultNs, "count"}, val: int64(10)}, 520 }) 521 assertMetricData(t, exporter, fleetAutoscalerCurrentReplicaCountName, []expectedMetricData{ 522 {labels: []string{"second-fleet", "name-switch", defaultNs}, val: int64(20)}, 523 }) 524 assertMetricData(t, exporter, fleetAutoscalersDesiredReplicaCountName, []expectedMetricData{ 525 {labels: []string{"second-fleet", "name-switch", defaultNs}, val: int64(10)}, 526 }) 527 assertMetricData(t, exporter, fleetAutoscalersLimitedName, []expectedMetricData{ 528 {labels: []string{"second-fleet", "name-switch", defaultNs}, val: int64(1)}, 529 }) 530 } 531 532 func TestControllerGameServersNodeState(t *testing.T) { 533 resetMetrics() 534 m := agtesting.NewMocks() 535 536 m.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 537 n1 := nodeWithName("node1") 538 n2 := nodeWithName("node2") 539 n3 := nodeWithName("node3") 540 return true, &corev1.NodeList{Items: []corev1.Node{*n1, *n2, *n3}}, nil 541 }) 542 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 543 gs1 := gameServerWithNode("node1") 544 gs2 := gameServerWithNode("node2") 545 gs3 := gameServerWithNode("node2") 546 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs1, *gs2, *gs3}}, nil 547 }) 548 549 c := newFakeControllerWithMock(m) 550 defer c.close() 551 require.True(t, c.sync()) 552 c.collect() 553 reader := metricexport.NewReader() 554 555 // wait until we have some nodes and gameservers in metrics 556 var exporter *metricExporter 557 assert.Eventually(t, func() bool { 558 exporter = &metricExporter{} 559 reader.ReadAndExport(exporter) 560 561 check := 0 562 for _, m := range exporter.metrics { 563 switch m.Descriptor.Name { 564 case nodeCountName: 565 check++ 566 case gameServersNodeCountName: 567 check++ 568 } 569 } 570 571 return check == 2 572 }, 10*time.Second, time.Second) 573 574 // check the details 575 assertMetricData(t, exporter, gameServersNodeCountName, []expectedMetricData{ 576 {labels: []string{}, val: &metricdata.Distribution{ 577 Count: 3, 578 Sum: 3, 579 SumOfSquaredDeviation: 2, 580 BucketOptions: &metricdata.BucketOptions{Bounds: []float64{0.00001, 1.00001, 2.00001, 3.00001, 4.00001, 5.00001, 6.00001, 7.00001, 8.00001, 9.00001, 10.00001, 11.00001, 12.00001, 13.00001, 14.00001, 15.00001, 16.00001, 32.00001, 40.00001, 50.00001, 60.00001, 70.00001, 80.00001, 90.00001, 100.00001, 110.00001, 120.00001}}, 581 Buckets: []metricdata.Bucket{{Count: 1}, {Count: 1}, {Count: 1}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}, {Count: 0}}}}, 582 }) 583 assertMetricData(t, exporter, nodeCountName, []expectedMetricData{ 584 {labels: []string{"true"}, val: int64(1)}, 585 {labels: []string{"false"}, val: int64(2)}, 586 }) 587 } 588 589 func TestFleetCountersAndListsMetrics(t *testing.T) { 590 runtime.FeatureTestMutex.Lock() 591 defer runtime.FeatureTestMutex.Unlock() 592 assert.NoError(t, runtime.ParseFeatures(string(runtime.FeatureCountsAndLists)+"=true")) 593 594 resetMetrics() 595 exporter := &metricExporter{} 596 reader := metricexport.NewReader() 597 c := newFakeController() 598 defer c.close() 599 600 fleetName := "cl-fleet-test" 601 counterName := "players" 602 counters := map[string]agonesv1.AggregatedCounterStatus{ 603 counterName: { 604 AllocatedCount: 24, 605 AllocatedCapacity: 30, 606 Count: 28, 607 Capacity: 50, 608 }, 609 } 610 listName := "rooms" 611 lists := map[string]agonesv1.AggregatedListStatus{ 612 listName: { 613 AllocatedCount: 4, 614 AllocatedCapacity: 6, 615 Count: 1, 616 Capacity: 100, 617 }, 618 } 619 620 f := fleet(fleetName, 8, 3, 5, 8, 0) 621 c.fleetWatch.Add(f) 622 f = f.DeepCopy() 623 f.Status.Lists = lists 624 f.Status.Counters = counters 625 c.fleetWatch.Modify(f) 626 627 c.run(t) 628 require.True(t, c.sync()) 629 require.Eventually(t, func() bool { 630 fl, err := c.fleetLister.Fleets(f.GetObjectMeta().GetNamespace()).Get(fleetName) 631 assert.NoError(t, err) 632 return cmp.Equal(counters, fl.Status.Counters) && cmp.Equal(lists, fl.Status.Lists) 633 }, 5*time.Second, time.Second) 634 c.collect() 635 636 reader.ReadAndExport(exporter) 637 assertMetricData(t, exporter, fleetCountersName, []expectedMetricData{ 638 // keyCounter, keyName, keyNamespace, keyType 639 {labels: []string{counterName, fleetName, defaultNs, "allocated_count"}, val: int64(24)}, 640 {labels: []string{counterName, fleetName, defaultNs, "allocated_capacity"}, val: int64(30)}, 641 {labels: []string{counterName, fleetName, defaultNs, "total_count"}, val: int64(28)}, 642 {labels: []string{counterName, fleetName, defaultNs, "total_capacity"}, val: int64(50)}, 643 }) 644 assertMetricData(t, exporter, fleetListsName, []expectedMetricData{ 645 // keyList, keyName, keyNamespace, keyType 646 {labels: []string{listName, fleetName, defaultNs, "allocated_count"}, val: int64(4)}, 647 {labels: []string{listName, fleetName, defaultNs, "allocated_capacity"}, val: int64(6)}, 648 {labels: []string{listName, fleetName, defaultNs, "total_count"}, val: int64(1)}, 649 {labels: []string{listName, fleetName, defaultNs, "total_capacity"}, val: int64(100)}, 650 }) 651 } 652 653 func TestCalcDuration(t *testing.T) { 654 m := agtesting.NewMocks() 655 c := NewController( 656 m.KubeClient, 657 m.AgonesClient, 658 m.KubeInformerFactory, 659 m.AgonesInformerFactory) 660 creationTimestamp := metav1.Now() 661 futureTimestamp := metav1.NewTime(time.Now().Add(24 * time.Hour)) 662 gsName1 := "exampleGameServer1" 663 gsName2 := "exampleGameServer2" 664 currentTime := creationTimestamp.Local() 665 // Add one second each time Duration is calculated 666 c.now = func() time.Time { 667 currentTime = currentTime.Add(1 * time.Second) 668 return currentTime 669 } 670 type result struct { 671 duration float64 672 err error 673 } 674 fleet1 := "test-fleet" 675 fleet2 := "" 676 var testCases = []struct { 677 description string 678 gs1 *agonesv1.GameServer 679 gs2 *agonesv1.GameServer 680 expected result 681 }{ 682 { 683 description: "GameServer creating - first measurement", 684 gs1: gameServerWithFleetStateCreationTimestamp(fleet1, gsName1, "", creationTimestamp), 685 gs2: gameServerWithFleetStateCreationTimestamp(fleet1, gsName1, agonesv1.GameServerStateCreating, creationTimestamp), 686 expected: result{ 687 err: nil, 688 duration: 1, 689 }, 690 }, 691 { 692 description: "Test state change of a GameServer", 693 gs1: gameServerWithFleetStateCreationTimestamp(fleet1, gsName1, agonesv1.GameServerStateCreating, creationTimestamp), 694 gs2: gameServerWithFleetStateCreationTimestamp(fleet1, gsName1, agonesv1.GameServerStateRequestReady, creationTimestamp), 695 expected: result{ 696 err: nil, 697 duration: 1, 698 }, 699 }, 700 { 701 description: "gs1 state should already be deleted, error should be generated (emulation of evicted key for gs1)", 702 gs1: gameServerWithFleetStateCreationTimestamp(fleet1, gsName1, "", creationTimestamp), 703 gs2: gameServerWithFleetStateCreationTimestamp(fleet1, gsName1, agonesv1.GameServerStateRequestReady, creationTimestamp), 704 expected: result{ 705 err: errors.Errorf("unable to calculate '' state duration of '%s' GameServer", gsName1), 706 duration: 0, 707 }, 708 }, 709 { 710 description: "Shutdown state should remove the key in LRU cache", 711 gs1: gameServerWithFleetStateCreationTimestamp(fleet1, gsName1, agonesv1.GameServerStateRequestReady, creationTimestamp), 712 gs2: gameServerWithFleetStateCreationTimestamp(fleet1, gsName1, agonesv1.GameServerStateShutdown, creationTimestamp), 713 expected: result{ 714 err: nil, 715 duration: 2, 716 }, 717 }, 718 { 719 description: "Cache miss, no key in LRU cache", 720 gs1: gameServerWithFleetStateCreationTimestamp(fleet1, gsName1, agonesv1.GameServerStateRequestReady, creationTimestamp), 721 gs2: gameServerWithFleetStateCreationTimestamp(fleet1, gsName1, agonesv1.GameServerStateShutdown, creationTimestamp), 722 expected: result{ 723 err: errors.Errorf("unable to calculate 'RequestReady' state duration of '%s' GameServer", gsName1), 724 duration: 0, 725 }, 726 }, 727 { 728 description: "Future timestamp was used", 729 gs1: gameServerWithFleetStateCreationTimestamp(fleet2, gsName2, "", futureTimestamp), 730 gs2: gameServerWithFleetStateCreationTimestamp(fleet2, gsName2, agonesv1.GameServerStateCreating, futureTimestamp), 731 expected: result{ 732 err: errors.Errorf("negative duration for '' state of '%s' GameServer", gsName2), 733 duration: 0, 734 }, 735 }, 736 { 737 description: "Shutdown state - remove a key from the LRU", 738 gs1: gameServerWithFleetStateCreationTimestamp(fleet2, gsName2, agonesv1.GameServerStateCreating, futureTimestamp), 739 gs2: gameServerWithFleetStateCreationTimestamp(fleet2, gsName2, agonesv1.GameServerStateShutdown, futureTimestamp), 740 expected: result{ 741 err: nil, 742 duration: 1, 743 }, 744 }, 745 } 746 747 for _, tc := range testCases { 748 t.Run(tc.description, func(t *testing.T) { 749 // Do not use t.Parallel(), because test cases should be executed as serial tests 750 // Test case 3 depends on key eviction in test case 2 751 duration, err := c.calcDuration(tc.gs1, tc.gs2) 752 if tc.expected.err != nil { 753 assert.EqualError(t, err, tc.expected.err.Error(), "We should receive an error, metric should not be measured") 754 } else { 755 assert.NoError(t, err, "Unable to caculate duration of a particular state") 756 } 757 assert.Equal(t, tc.expected.duration, duration, "Time diff should be calculated properly") 758 }) 759 } 760 assert.Len(t, c.gameServerStateLastChange.Keys(), 0, "We should not have any keys after the test") 761 } 762 763 func TestIsSystemNode(t *testing.T) { 764 cases := []struct { 765 desc string 766 node *corev1.Node 767 expected bool 768 }{ 769 { 770 desc: "Is system node, true expected", 771 node: &corev1.Node{ 772 Spec: corev1.NodeSpec{ 773 Taints: []corev1.Taint{{Key: "agones.dev/test"}}, 774 }, 775 }, 776 expected: true, 777 }, 778 { 779 desc: "Not a system node, false expected", 780 node: &corev1.Node{ 781 Spec: corev1.NodeSpec{ 782 Taints: []corev1.Taint{{Key: "qwerty.dev/test"}}, 783 }, 784 }, 785 expected: false, 786 }, 787 { 788 desc: "Empty taints, false expected", 789 node: &corev1.Node{ 790 Spec: corev1.NodeSpec{ 791 Taints: []corev1.Taint{}, 792 }, 793 }, 794 expected: false, 795 }, 796 } 797 798 for _, tc := range cases { 799 t.Run(tc.desc, func(t *testing.T) { 800 res := isSystemNode(tc.node) 801 assert.Equal(t, tc.expected, res) 802 }) 803 } 804 }