agones.dev/agones@v1.54.0/pkg/gameserverallocations/metrics_test.go (about) 1 // Copyright 2022 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 gameserverallocations 16 17 import ( 18 "bufio" 19 "context" 20 "net/http" 21 "strings" 22 "testing" 23 "time" 24 25 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 26 allocationv1 "agones.dev/agones/pkg/apis/allocation/v1" 27 gameserverv1 "agones.dev/agones/pkg/client/listers/agones/v1" 28 mt "agones.dev/agones/pkg/metrics" 29 agtesting "agones.dev/agones/pkg/testing" 30 "agones.dev/agones/pkg/util/httpserver" 31 "agones.dev/agones/pkg/util/runtime" 32 "agones.dev/agones/test/e2e/framework" 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 "go.opencensus.io/stats/view" 36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 37 "k8s.io/apimachinery/pkg/labels" 38 k8sruntime "k8s.io/apimachinery/pkg/runtime" 39 "k8s.io/apimachinery/pkg/util/wait" 40 "k8s.io/apimachinery/pkg/watch" 41 k8stesting "k8s.io/client-go/testing" 42 ) 43 44 type mockGameServerLister struct { 45 gameServerNamespaceLister mockGameServerNamespaceLister 46 gameServersCalled bool 47 } 48 49 type mockGameServerNamespaceLister struct { 50 gameServer *agonesv1.GameServer 51 } 52 53 func (s *mockGameServerLister) List(_ labels.Selector) (ret []*agonesv1.GameServer, err error) { 54 return ret, nil 55 } 56 57 func (s *mockGameServerLister) GameServers(_ string) gameserverv1.GameServerNamespaceLister { 58 s.gameServersCalled = true 59 return s.gameServerNamespaceLister 60 } 61 62 func (s mockGameServerNamespaceLister) Get(_ string) (*agonesv1.GameServer, error) { 63 return s.gameServer, nil 64 } 65 66 func (s mockGameServerNamespaceLister) List(_ labels.Selector) (ret []*agonesv1.GameServer, err error) { 67 return ret, nil 68 } 69 70 func resetMetrics() { 71 unRegisterViews() 72 registerViews() 73 } 74 75 func TestSetResponse(t *testing.T) { 76 subtests := []struct { 77 name string 78 gameServer *agonesv1.GameServer 79 err error 80 allocation *allocationv1.GameServerAllocation 81 expectedState allocationv1.GameServerAllocationState 82 expectedCalled bool 83 }{ 84 { 85 name: "Try to get gs from local cluster for local allocation", 86 gameServer: &agonesv1.GameServer{ 87 ObjectMeta: metav1.ObjectMeta{ 88 Labels: map[string]string{agonesv1.FleetNameLabel: "fleetName"}, 89 }, 90 }, 91 allocation: &allocationv1.GameServerAllocation{ 92 Status: allocationv1.GameServerAllocationStatus{ 93 State: allocationv1.GameServerAllocationAllocated, 94 GameServerName: "gameServerName", 95 Source: "local", 96 }, 97 }, 98 expectedCalled: true, 99 }, 100 { 101 name: "Do not try to get gs from local cluster for remote allocation", 102 gameServer: &agonesv1.GameServer{ 103 ObjectMeta: metav1.ObjectMeta{ 104 Labels: map[string]string{agonesv1.FleetNameLabel: "fleetName"}, 105 }, 106 }, 107 allocation: &allocationv1.GameServerAllocation{ 108 Status: allocationv1.GameServerAllocationStatus{ 109 State: allocationv1.GameServerAllocationAllocated, 110 GameServerName: "gameServerName", 111 Source: "33.188.237.156:443", 112 }, 113 }, 114 expectedCalled: false, 115 }, 116 } 117 118 for _, subtest := range subtests { 119 gsl := mockGameServerLister{ 120 gameServerNamespaceLister: mockGameServerNamespaceLister{ 121 gameServer: subtest.gameServer, 122 }, 123 } 124 125 metrics := metrics{ 126 ctx: context.Background(), 127 gameServerLister: &gsl, 128 logger: runtime.NewLoggerWithSource("metrics_test"), 129 start: time.Now(), 130 } 131 132 t.Run(subtest.name, func(t *testing.T) { 133 metrics.setResponse(subtest.allocation) 134 assert.Equal(t, subtest.expectedCalled, gsl.gameServersCalled) 135 }) 136 } 137 } 138 139 func TestAllocationMetrics(t *testing.T) { 140 resetMetrics() 141 142 runtime.FeatureTestMutex.Lock() 143 defer runtime.FeatureTestMutex.Unlock() 144 145 conf := mt.Config{ 146 PrometheusMetrics: true, 147 } 148 server := &httpserver.Server{ 149 Port: "3001", 150 Logger: framework.TestLogger(t), 151 } 152 153 health, closer := mt.SetupMetrics(conf, server) 154 defer t.Cleanup(closer) 155 156 assert.NotNil(t, health, "Health check handler should not be nil") 157 server.Handle("/", health) 158 159 f, gsList := defaultFixtures(1) 160 a, m := newFakeAllocator() 161 162 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 163 return true, &agonesv1.GameServerList{Items: gsList}, nil 164 }) 165 166 gsWatch := watch.NewFake() 167 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil)) 168 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) { 169 ua := action.(k8stesting.UpdateAction) 170 gs := ua.GetObject().(*agonesv1.GameServer) 171 assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State) 172 gsWatch.Modify(gs) 173 174 return true, gs, nil 175 }) 176 177 ctxAlloc, cancelAlloc := agtesting.StartInformers(m, a.allocationCache.gameServerSynced) 178 defer cancelAlloc() 179 180 require.NoError(t, a.Run(ctxAlloc)) 181 // wait for it to be up and running 182 err := wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (done bool, err error) { 183 return a.allocationCache.workerqueue.RunCount() == 1, nil 184 }) 185 require.NoError(t, err) 186 187 gsa := allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{Name: "gsa-1", Namespace: defaultNs}, 188 Spec: allocationv1.GameServerAllocationSpec{ 189 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: f.ObjectMeta.Name}}}}, 190 }} 191 gsa.ApplyDefaults() 192 errs := gsa.Validate() 193 require.Len(t, errs, 0) 194 195 result, err := a.Allocate(ctxAlloc, &gsa) 196 require.NoError(t, err) 197 require.NotNil(t, result) 198 199 ctxHTTP, cancelHTTP := context.WithCancel(context.Background()) 200 defer cancelHTTP() 201 202 // Start the HTTP server 203 go func() { 204 _ = server.Run(ctxHTTP, 0) 205 }() 206 time.Sleep(300 * time.Millisecond) 207 208 resp, err := http.Get("http://localhost:3001/metrics") 209 require.NoError(t, err, "Failed to GET metrics endpoint") 210 defer func() { 211 assert.NoError(t, resp.Body.Close()) 212 }() 213 214 assert.Equal(t, http.StatusOK, resp.StatusCode, "Expected status code 200") 215 216 metricsSet := collectMetricNames(resp) 217 expectedMetrics := getMetricNames() 218 219 for _, metric := range expectedMetrics { 220 assert.Contains(t, metricsSet, metric, "Missing expected metric: %s", metric) 221 } 222 } 223 224 // getMetricNames returns all metric view names. 225 func getMetricNames() []string { 226 var metricNames []string 227 for _, v := range stateViews { 228 metricName := "agones_" + v.Name 229 230 // Check if the aggregation type is Distribution 231 if v.Aggregation.Type == view.AggTypeDistribution { 232 // If it's a distribution, we append _bucket, _sum, and _count 233 metricNames = append(metricNames, 234 metricName+"_bucket", 235 metricName+"_sum", 236 metricName+"_count", 237 ) 238 } else { 239 metricNames = append(metricNames, metricName) 240 241 } 242 } 243 return metricNames 244 } 245 246 func collectMetricNames(resp *http.Response) map[string]bool { 247 metrics := make(map[string]bool) 248 scanner := bufio.NewScanner(resp.Body) 249 for scanner.Scan() { 250 line := scanner.Text() 251 if strings.HasPrefix(line, "#") || line == "" { 252 continue 253 } 254 fields := strings.Fields(line) 255 if len(fields) > 0 { 256 // Extract only the metric name, excluding labels 257 metricName := fields[0] 258 if idx := strings.Index(metricName, "{"); idx != -1 { 259 metricName = metricName[:idx] 260 } 261 metrics[metricName] = true 262 } 263 } 264 return metrics 265 }