agones.dev/agones@v1.53.0/pkg/fleetautoscalers/fleetautoscalerwasm_test.go (about) 1 /* 2 * Copyright 2025 Google LLC All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package fleetautoscalers 18 19 import ( 20 "context" 21 "crypto/sha256" 22 "encoding/hex" 23 "net/http" 24 "net/http/httptest" 25 "os" 26 "path/filepath" 27 "testing" 28 29 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 30 autoscalingv1 "agones.dev/agones/pkg/apis/autoscaling/v1" 31 utilruntime "agones.dev/agones/pkg/util/runtime" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 ) 35 36 // defaultWasmFixtures creates default fixtures for testing WasmPolicy 37 func defaultWasmFixtures() (*autoscalingv1.FleetAutoscaler, *agonesv1.Fleet) { 38 fas, f := defaultFixtures() 39 fas.Spec.Policy.Type = autoscalingv1.WasmPolicyType 40 fas.Spec.Policy.Buffer = nil 41 42 // Set up WasmPolicy 43 url := "plugin.wasm" 44 fas.Spec.Policy.Wasm = &autoscalingv1.WasmPolicy{ 45 Function: "scale", 46 Config: map[string]string{ 47 "buffer_size": "5", 48 }, 49 From: autoscalingv1.WasmFrom{ 50 URL: &autoscalingv1.URLConfiguration{ 51 URL: &url, 52 }, 53 }, 54 } 55 56 return fas, f 57 } 58 59 func TestApplyWasmPolicy(t *testing.T) { 60 t.Parallel() 61 62 // Enable the WASM autoscaler feature flag for testing 63 utilruntime.FeatureTestMutex.Lock() 64 defer utilruntime.FeatureTestMutex.Unlock() 65 utilruntime.EnableAllFeatures() 66 67 // Find the WASM plugin file 68 wasmFilePath, err := filepath.Abs(filepath.Join("..", "..", "examples", "autoscaler-wasm")) 69 require.NoError(t, err) 70 71 _, err = os.Stat(wasmFilePath) 72 require.NoError(t, err, "WASM plugin file not found at %s", wasmFilePath) 73 74 // Create a test server to serve the WASM plugin 75 svrDir := http.Dir(wasmFilePath) 76 sourcePluginPath := filepath.Join(wasmFilePath, "plugin.wasm") 77 // Compute the SHA256 hash of the plugin for Hash tests 78 pluginBytes, err := os.ReadFile(sourcePluginPath) 79 require.NoError(t, err) 80 sum := sha256.Sum256(pluginBytes) 81 hashStr := hex.EncodeToString(sum[:]) 82 // Create an incorrect hash (same length, wrong value) for negative test 83 badSum := make([]byte, len(sum)) 84 copy(badSum, sum[:]) 85 badSum[0] ^= 0xFF // flip first byte to ensure mismatch 86 badHash := hex.EncodeToString(badSum) 87 88 sourceFS := http.FileServer(svrDir) 89 srv := httptest.NewServer(sourceFS) 90 defer srv.Close() 91 92 // Create test fixtures 93 fas, f := defaultWasmFixtures() 94 95 // Update the URL to point to our test server 96 fileURL := srv.URL + "/plugin.wasm" 97 fas.Spec.Policy.Wasm.From.URL.URL = &fileURL 98 99 // Create a logger for testing 100 logger := &FasLogger{ 101 fas: fas, 102 baseLogger: newTestLogger(), 103 } 104 105 type expected struct { 106 replicas int32 107 limited bool 108 err string 109 } 110 111 type testCase struct { 112 wasmPolicy *autoscalingv1.WasmPolicy 113 fleet *agonesv1.Fleet 114 specReplicas int32 115 statusReplicas int32 116 statusAllocatedReplicas int32 117 statusReadyReplicas int32 118 expected expected 119 } 120 121 var testCases = map[string]testCase{ 122 "Correct Hash provided (sha256), scale up needed": { 123 wasmPolicy: &autoscalingv1.WasmPolicy{ 124 Function: "scale", 125 Config: map[string]string{ 126 "buffer_size": "5", 127 }, 128 From: autoscalingv1.WasmFrom{ 129 URL: &autoscalingv1.URLConfiguration{ 130 URL: &fileURL, 131 }, 132 }, 133 Hash: hashStr, 134 }, 135 fleet: f, 136 specReplicas: 10, 137 statusReplicas: 10, 138 statusAllocatedReplicas: 8, 139 statusReadyReplicas: 2, 140 expected: expected{ 141 replicas: 13, 142 limited: false, 143 err: "", 144 }, 145 }, 146 "Incorrect Hash provided (sha256), plugin creation fails": { 147 wasmPolicy: &autoscalingv1.WasmPolicy{ 148 Function: "scale", 149 Config: map[string]string{ 150 "buffer_size": "5", 151 }, 152 From: autoscalingv1.WasmFrom{ 153 URL: &autoscalingv1.URLConfiguration{ 154 URL: &fileURL, 155 }, 156 }, 157 Hash: badHash, 158 }, 159 fleet: f, 160 expected: expected{ 161 replicas: 0, 162 limited: false, 163 err: "hash mismatch for module", 164 }, 165 }, 166 "Default buffer size (5), scale up needed": { 167 wasmPolicy: &autoscalingv1.WasmPolicy{ 168 Function: "scale", 169 Config: map[string]string{ 170 "buffer_size": "5", 171 }, 172 From: autoscalingv1.WasmFrom{ 173 URL: &autoscalingv1.URLConfiguration{ 174 URL: &fileURL, 175 }, 176 }, 177 }, 178 fleet: f, 179 specReplicas: 10, 180 statusReplicas: 10, 181 statusAllocatedReplicas: 8, 182 statusReadyReplicas: 2, 183 expected: expected{ 184 replicas: 13, // allocated (8) + buffer (5) 185 limited: false, 186 err: "", 187 }, 188 }, 189 "Default buffer size (5), no scaling needed": { 190 wasmPolicy: &autoscalingv1.WasmPolicy{ 191 Function: "scale", 192 Config: map[string]string{ 193 "buffer_size": "5", 194 }, 195 From: autoscalingv1.WasmFrom{ 196 URL: &autoscalingv1.URLConfiguration{ 197 URL: &fileURL, 198 }, 199 }, 200 }, 201 fleet: f, 202 specReplicas: 15, 203 statusReplicas: 15, 204 statusAllocatedReplicas: 10, 205 statusReadyReplicas: 5, 206 expected: expected{ 207 replicas: 15, // already at the right size 208 limited: false, 209 err: "", 210 }, 211 }, 212 "Custom buffer size (10), scale up needed": { 213 wasmPolicy: &autoscalingv1.WasmPolicy{ 214 Function: "scale", 215 Config: map[string]string{ 216 "buffer_size": "10", 217 }, 218 From: autoscalingv1.WasmFrom{ 219 URL: &autoscalingv1.URLConfiguration{ 220 URL: &fileURL, 221 }, 222 }, 223 }, 224 fleet: f, 225 specReplicas: 15, 226 statusReplicas: 15, 227 statusAllocatedReplicas: 10, 228 statusReadyReplicas: 5, 229 expected: expected{ 230 replicas: 20, // allocated (10) + buffer (10) 231 limited: false, 232 err: "", 233 }, 234 }, 235 "nil WasmPolicy, error returned": { 236 wasmPolicy: nil, 237 fleet: f, 238 expected: expected{ 239 replicas: 0, 240 limited: false, 241 err: "wasmPolicy parameter must not be nil", 242 }, 243 }, 244 "nil Fleet, error returned": { 245 wasmPolicy: &autoscalingv1.WasmPolicy{ 246 Function: "scale", 247 Config: map[string]string{ 248 "buffer_size": "5", 249 }, 250 From: autoscalingv1.WasmFrom{ 251 URL: &autoscalingv1.URLConfiguration{ 252 URL: &fileURL, 253 }, 254 }, 255 }, 256 fleet: nil, 257 expected: expected{ 258 replicas: 0, 259 limited: false, 260 err: "fleet parameter must not be nil", 261 }, 262 }, 263 "Invalid URL in WasmPolicy": { 264 wasmPolicy: &autoscalingv1.WasmPolicy{ 265 Function: "scale", 266 Config: map[string]string{ 267 "buffer_size": "5", 268 }, 269 From: autoscalingv1.WasmFrom{ 270 URL: &autoscalingv1.URLConfiguration{ 271 URL: nil, 272 }, 273 }, 274 }, 275 fleet: f, 276 expected: expected{ 277 replicas: 0, 278 limited: false, 279 err: "service was not provided, either URL or Service must be provided", 280 }, 281 }, 282 "Function set to scaleNone, no scaling occurs": { 283 wasmPolicy: &autoscalingv1.WasmPolicy{ 284 Function: "scaleNone", 285 From: autoscalingv1.WasmFrom{ 286 URL: &autoscalingv1.URLConfiguration{ 287 URL: &fileURL, 288 }, 289 }, 290 }, 291 fleet: f, 292 specReplicas: 10, 293 statusReplicas: 10, 294 statusAllocatedReplicas: 8, 295 statusReadyReplicas: 2, 296 expected: expected{ 297 replicas: 10, 298 limited: false, 299 err: "", 300 }, 301 }, 302 } 303 304 for name, tc := range testCases { 305 t.Run(name, func(t *testing.T) { 306 307 var fleet *agonesv1.Fleet 308 if tc.fleet != nil { 309 fleet = tc.fleet.DeepCopy() 310 fleet.Spec.Replicas = tc.specReplicas 311 fleet.Status.Replicas = tc.statusReplicas 312 fleet.Status.AllocatedReplicas = tc.statusAllocatedReplicas 313 fleet.Status.ReadyReplicas = tc.statusReadyReplicas 314 } 315 316 // Create a new state map for each test case 317 state := make(map[string]any) 318 319 replicas, limited, err := applyWasmPolicy(context.Background(), state, tc.wasmPolicy, fleet, logger) 320 321 if tc.expected.err != "" { 322 require.ErrorContains(t, err, tc.expected.err) 323 } else { 324 require.NoError(t, err) 325 assert.Equal(t, tc.expected.replicas, replicas) 326 assert.Equal(t, tc.expected.limited, limited) 327 } 328 }) 329 } 330 }