agones.dev/agones@v1.54.0/pkg/fleetautoscalers/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 fleetautoscalers
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"sync/atomic"
    24  	"testing"
    25  	"time"
    26  
    27  	"agones.dev/agones/pkg/apis"
    28  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    29  	autoscalingv1 "agones.dev/agones/pkg/apis/autoscaling/v1"
    30  	"agones.dev/agones/pkg/gameservers"
    31  	agtesting "agones.dev/agones/pkg/testing"
    32  	"agones.dev/agones/pkg/util/webhooks"
    33  	"github.com/heptiolabs/healthcheck"
    34  	"github.com/pkg/errors"
    35  	"github.com/stretchr/testify/assert"
    36  	"github.com/stretchr/testify/require"
    37  	"gomodules.xyz/jsonpatch/v2"
    38  	admissionv1 "k8s.io/api/admission/v1"
    39  	admregv1 "k8s.io/api/admissionregistration/v1"
    40  	corev1 "k8s.io/api/core/v1"
    41  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    42  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    43  	"k8s.io/apimachinery/pkg/runtime"
    44  	"k8s.io/apimachinery/pkg/types"
    45  	"k8s.io/apimachinery/pkg/util/intstr"
    46  	"k8s.io/apimachinery/pkg/watch"
    47  	k8stesting "k8s.io/client-go/testing"
    48  	"k8s.io/client-go/tools/cache"
    49  )
    50  
    51  var (
    52  	gvk = metav1.GroupVersionKind(agonesv1.SchemeGroupVersion.WithKind("FleetAutoscaler"))
    53  )
    54  
    55  func TestControllerCreationMutationHandler(t *testing.T) {
    56  	t.Parallel()
    57  
    58  	type expected struct {
    59  		responseAllowed bool
    60  		patches         []jsonpatch.JsonPatchOperation
    61  		nilPatch        bool
    62  	}
    63  
    64  	var testCases = []struct {
    65  		description string
    66  		fixture     interface{}
    67  		expected    expected
    68  	}{
    69  		{
    70  			description: "OK",
    71  			fixture: &autoscalingv1.FleetAutoscaler{
    72  				ObjectMeta: metav1.ObjectMeta{
    73  					Name:       "fas-1",
    74  					Namespace:  "default",
    75  					Generation: 2,
    76  				},
    77  				Spec: autoscalingv1.FleetAutoscalerSpec{
    78  					FleetName: "fleet-1",
    79  					Policy: autoscalingv1.FleetAutoscalerPolicy{
    80  						Type: autoscalingv1.BufferPolicyType,
    81  						Buffer: &autoscalingv1.BufferPolicy{
    82  							BufferSize:  intstr.FromInt(5),
    83  							MaxReplicas: 100,
    84  						},
    85  					},
    86  					Sync: &autoscalingv1.FleetAutoscalerSync{
    87  						Type: autoscalingv1.FixedIntervalSyncType,
    88  						FixedInterval: autoscalingv1.FixedIntervalSync{
    89  							Seconds: 30,
    90  						},
    91  					},
    92  				},
    93  			},
    94  			expected: expected{
    95  				responseAllowed: true,
    96  				patches:         []jsonpatch.JsonPatchOperation{},
    97  			},
    98  		},
    99  		{
   100  			description: "OK",
   101  			// Spec.Sync is not defined
   102  			fixture: &autoscalingv1.FleetAutoscaler{
   103  				ObjectMeta: metav1.ObjectMeta{
   104  					Name:       "fas-1",
   105  					Namespace:  "default",
   106  					Generation: 2,
   107  				},
   108  				Spec: autoscalingv1.FleetAutoscalerSpec{
   109  					FleetName: "fleet-1",
   110  					Policy: autoscalingv1.FleetAutoscalerPolicy{
   111  						Type: autoscalingv1.BufferPolicyType,
   112  						Buffer: &autoscalingv1.BufferPolicy{
   113  							BufferSize:  intstr.FromInt(5),
   114  							MaxReplicas: 100,
   115  						},
   116  					},
   117  				},
   118  			},
   119  			expected: expected{
   120  				responseAllowed: true,
   121  				patches: []jsonpatch.JsonPatchOperation{
   122  					{
   123  						Operation: "add",
   124  						Path:      "/spec/sync",
   125  						Value: map[string]interface{}{
   126  							"fixedInterval": map[string]interface{}{
   127  								"seconds": float64(30),
   128  							},
   129  							"type": "FixedInterval",
   130  						},
   131  					},
   132  				},
   133  			},
   134  		},
   135  		{
   136  			description: "Wrong request object, err expected",
   137  			fixture:     "WRONG DATA",
   138  			expected:    expected{nilPatch: true},
   139  		},
   140  	}
   141  
   142  	ext := newFakeExtensions()
   143  
   144  	for _, tc := range testCases {
   145  		t.Run(tc.description, func(t *testing.T) {
   146  			raw, err := json.Marshal(tc.fixture)
   147  			require.NoError(t, err)
   148  
   149  			review := admissionv1.AdmissionReview{
   150  				Request: &admissionv1.AdmissionRequest{
   151  					Kind:      gvk,
   152  					Operation: admissionv1.Create,
   153  					Object: runtime.RawExtension{
   154  						Raw: raw,
   155  					},
   156  				},
   157  				Response: &admissionv1.AdmissionResponse{Allowed: true},
   158  			}
   159  
   160  			result, err := ext.mutationHandler(review)
   161  
   162  			assert.NoError(t, err)
   163  			if tc.expected.nilPatch {
   164  				require.Nil(t, result.Response.PatchType)
   165  			} else {
   166  				assert.True(t, result.Response.Allowed)
   167  				assert.Equal(t, admissionv1.PatchTypeJSONPatch, *result.Response.PatchType)
   168  
   169  				patch := &jsonpatch.ByPath{}
   170  				err = json.Unmarshal(result.Response.Patch, patch)
   171  				require.NoError(t, err)
   172  
   173  				found := false
   174  
   175  				for _, expected := range tc.expected.patches {
   176  					for _, p := range *patch {
   177  						if assert.ObjectsAreEqual(p, expected) {
   178  							found = true
   179  						}
   180  					}
   181  					assert.True(t, found, "Could not find operation %#v in patch %v", expected, *patch)
   182  				}
   183  			}
   184  		})
   185  	}
   186  }
   187  
   188  func TestControllerCreationValidationHandler(t *testing.T) {
   189  	t.Parallel()
   190  
   191  	t.Run("valid fleet autoscaler", func(t *testing.T) {
   192  		_, m := newFakeController()
   193  		ext := newFakeExtensions()
   194  		fas, _ := defaultFixtures()
   195  		_, cancel := agtesting.StartInformers(m)
   196  		defer cancel()
   197  
   198  		review, err := newAdmissionReview(*fas)
   199  		assert.Nil(t, err)
   200  
   201  		result, err := ext.validationHandler(review)
   202  		assert.Nil(t, err)
   203  		assert.True(t, result.Response.Allowed, fmt.Sprintf("%#v", result.Response))
   204  	})
   205  
   206  	t.Run("invalid fleet autoscaler", func(t *testing.T) {
   207  		_, m := newFakeController()
   208  		ext := newFakeExtensions()
   209  		fas, _ := defaultFixtures()
   210  		// this make it invalid
   211  		fas.Spec.Policy.Buffer = nil
   212  
   213  		_, cancel := agtesting.StartInformers(m)
   214  		defer cancel()
   215  
   216  		review, err := newAdmissionReview(*fas)
   217  		assert.Nil(t, err)
   218  
   219  		result, err := ext.validationHandler(review)
   220  		assert.Nil(t, err)
   221  		assert.False(t, result.Response.Allowed, fmt.Sprintf("%#v", result.Response))
   222  		assert.Equal(t, metav1.StatusFailure, result.Response.Result.Status)
   223  		assert.Equal(t, metav1.StatusReasonInvalid, result.Response.Result.Reason)
   224  		assert.NotEmpty(t, result.Response.Result.Details)
   225  	})
   226  
   227  	t.Run("unable to unmarshal AdmissionRequest", func(t *testing.T) {
   228  		ext := newFakeExtensions()
   229  
   230  		review, err := newInvalidAdmissionReview()
   231  		assert.Nil(t, err)
   232  
   233  		_, err = ext.validationHandler(review)
   234  
   235  		if assert.NotNil(t, err) {
   236  			assert.Equal(t, "error unmarshalling FleetAutoscaler json after schema validation: \"MQ==\": json: cannot unmarshal string into Go value of type v1.FleetAutoscaler", err.Error())
   237  		}
   238  	})
   239  }
   240  
   241  func TestWebhookControllerCreationValidationHandler(t *testing.T) {
   242  	t.Parallel()
   243  
   244  	t.Run("valid fleet autoscaler", func(t *testing.T) {
   245  		_, m := newFakeController()
   246  		ext := newFakeExtensions()
   247  		fas, _ := defaultWebhookFixtures()
   248  		_, cancel := agtesting.StartInformers(m)
   249  		defer cancel()
   250  
   251  		review, err := newAdmissionReview(*fas)
   252  		assert.Nil(t, err)
   253  
   254  		result, err := ext.validationHandler(review)
   255  		assert.Nil(t, err)
   256  		assert.True(t, result.Response.Allowed, fmt.Sprintf("%#v", result.Response))
   257  	})
   258  
   259  	t.Run("invalid fleet autoscaler", func(t *testing.T) {
   260  		_, m := newFakeController()
   261  		ext := newFakeExtensions()
   262  		fas, _ := defaultWebhookFixtures()
   263  		// this make it invalid
   264  		fas.Spec.Policy.Webhook = nil
   265  
   266  		_, cancel := agtesting.StartInformers(m)
   267  		defer cancel()
   268  
   269  		review, err := newAdmissionReview(*fas)
   270  		assert.Nil(t, err)
   271  
   272  		result, err := ext.validationHandler(review)
   273  		assert.Nil(t, err)
   274  		assert.False(t, result.Response.Allowed, fmt.Sprintf("%#v", result.Response))
   275  		assert.Equal(t, metav1.StatusFailure, result.Response.Result.Status)
   276  		assert.Equal(t, metav1.StatusReasonInvalid, result.Response.Result.Reason)
   277  		assert.NotEmpty(t, result.Response.Result.Details)
   278  	})
   279  }
   280  
   281  // nolint:dupl
   282  func TestControllerSyncFleetAutoscaler(t *testing.T) {
   283  
   284  	t.Run("no scaling up because fleet is marked for deletion, buffer policy", func(t *testing.T) {
   285  		t.Parallel()
   286  		c, m := newFakeController()
   287  		fas, f := defaultFixtures()
   288  		fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(7)
   289  
   290  		f.Spec.Replicas = 5
   291  		f.Status.Replicas = 5
   292  		f.Status.AllocatedReplicas = 5
   293  		f.Status.ReadyReplicas = 0
   294  		f.DeletionTimestamp = &metav1.Time{
   295  			Time: time.Now(),
   296  		}
   297  
   298  		m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   299  			return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
   300  		})
   301  
   302  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   303  			assert.FailNow(t, "fleetautoscaler should not update")
   304  			return false, nil, nil
   305  		})
   306  
   307  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   308  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   309  		})
   310  
   311  		m.AgonesClient.AddReactor("update", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   312  			assert.FailNow(t, "fleet should not update")
   313  			return false, nil, nil
   314  		})
   315  
   316  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   317  		defer cancel()
   318  		fleetAutoscalerThreadEventually(t, c, fas)
   319  
   320  		err := c.syncFleetAutoscaler(ctx, "default/fas-1")
   321  		assert.Nil(t, err)
   322  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   323  	})
   324  
   325  	t.Run("scaling up, buffer policy", func(t *testing.T) {
   326  		t.Parallel()
   327  		c, m := newFakeController()
   328  		fas, f := defaultFixtures()
   329  		fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(7)
   330  
   331  		f.Spec.Replicas = 5
   332  		f.Status.Replicas = 5
   333  		f.Status.AllocatedReplicas = 5
   334  		f.Status.ReadyReplicas = 0
   335  
   336  		fUpdated := false
   337  		fasUpdated := false
   338  
   339  		m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   340  			return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
   341  		})
   342  
   343  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   344  			fasUpdated = true
   345  			ca := action.(k8stesting.UpdateAction)
   346  			fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler)
   347  			assert.Equal(t, fas.Status.AbleToScale, true)
   348  			assert.Equal(t, fas.Status.ScalingLimited, false)
   349  			assert.Equal(t, fas.Status.CurrentReplicas, int32(5))
   350  			assert.Equal(t, fas.Status.DesiredReplicas, int32(12))
   351  			assert.Equal(t, fas.Status.LastAppliedPolicy, autoscalingv1.BufferPolicyType)
   352  			assert.NotNil(t, fas.Status.LastScaleTime)
   353  			return true, fas, nil
   354  		})
   355  
   356  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   357  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   358  		})
   359  
   360  		m.AgonesClient.AddReactor("update", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) {
   361  			fUpdated = true
   362  			ca := action.(k8stesting.UpdateAction)
   363  			f := ca.GetObject().(*agonesv1.Fleet)
   364  			assert.Equal(t, f.Spec.Replicas, int32(12))
   365  			return true, f, nil
   366  		})
   367  
   368  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   369  		defer cancel()
   370  		fleetAutoscalerThreadEventually(t, c, fas)
   371  
   372  		err := c.syncFleetAutoscaler(ctx, "default/fas-1")
   373  		assert.Nil(t, err)
   374  		assert.True(t, fUpdated, "fleet should have been updated")
   375  		assert.True(t, fasUpdated, "fleetautoscaler should have been updated")
   376  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "AutoScalingFleet")
   377  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   378  	})
   379  
   380  	t.Run("scaling up, webhook policy", func(t *testing.T) {
   381  		t.Parallel()
   382  		c, m := newFakeController()
   383  		fas, f := defaultWebhookFixtures()
   384  		f.Spec.Replicas = 50
   385  		f.Status.Replicas = f.Spec.Replicas
   386  		f.Status.AllocatedReplicas = 45
   387  		f.Status.ReadyReplicas = 0
   388  
   389  		ts := testServer{}
   390  		server := httptest.NewServer(ts)
   391  		defer server.Close()
   392  
   393  		fas.Spec.Policy.Webhook.URL = &(server.URL)
   394  		fas.Spec.Policy.Webhook.Service = nil
   395  
   396  		fUpdated := false
   397  		fasUpdated := false
   398  
   399  		m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   400  			return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
   401  		})
   402  
   403  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   404  			fasUpdated = true
   405  			ca := action.(k8stesting.UpdateAction)
   406  			fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler)
   407  			assert.Equal(t, fas.Status.AbleToScale, true)
   408  			assert.Equal(t, fas.Status.ScalingLimited, false)
   409  			assert.Equal(t, fas.Status.CurrentReplicas, int32(50))
   410  			assert.Equal(t, fas.Status.DesiredReplicas, int32(100))
   411  			assert.Equal(t, fas.Status.LastAppliedPolicy, autoscalingv1.WebhookPolicyType)
   412  			assert.NotNil(t, fas.Status.LastScaleTime)
   413  			return true, fas, nil
   414  		})
   415  
   416  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   417  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   418  		})
   419  
   420  		m.AgonesClient.AddReactor("update", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) {
   421  			fUpdated = true
   422  			ca := action.(k8stesting.UpdateAction)
   423  			f := ca.GetObject().(*agonesv1.Fleet)
   424  			assert.Equal(t, f.Spec.Replicas, int32(100))
   425  			return true, f, nil
   426  		})
   427  
   428  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   429  		defer cancel()
   430  		fleetAutoscalerThreadEventually(t, c, fas)
   431  
   432  		err := c.syncFleetAutoscaler(ctx, "default/fas-1")
   433  		assert.Nil(t, err)
   434  		assert.True(t, fUpdated, "fleet should have been updated")
   435  		assert.True(t, fasUpdated, "fleetautoscaler should have been updated")
   436  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "AutoScalingFleet")
   437  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   438  	})
   439  
   440  	t.Run("no scaling up because fleet is marked for deletion, webhook policy", func(t *testing.T) {
   441  		t.Parallel()
   442  		c, m := newFakeController()
   443  		fas, f := defaultWebhookFixtures()
   444  		f.Spec.Replicas = 50
   445  		f.Status.Replicas = f.Spec.Replicas
   446  		f.Status.AllocatedReplicas = 45
   447  		f.Status.ReadyReplicas = 0
   448  		f.DeletionTimestamp = &metav1.Time{
   449  			Time: time.Now(),
   450  		}
   451  
   452  		ts := testServer{}
   453  		server := httptest.NewServer(ts)
   454  		defer server.Close()
   455  
   456  		fas.Spec.Policy.Webhook.URL = &(server.URL)
   457  		fas.Spec.Policy.Webhook.Service = nil
   458  
   459  		m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   460  			return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
   461  		})
   462  
   463  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   464  			assert.FailNow(t, "fleetautoscaler should not update")
   465  			return false, nil, nil
   466  		})
   467  
   468  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   469  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   470  		})
   471  
   472  		m.AgonesClient.AddReactor("update", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   473  			assert.FailNow(t, "fleet should not update")
   474  			return false, nil, nil
   475  		})
   476  
   477  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   478  		defer cancel()
   479  		fleetAutoscalerThreadEventually(t, c, fas)
   480  
   481  		err := c.syncFleetAutoscaler(ctx, "default/fas-1")
   482  		assert.Nil(t, err)
   483  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   484  	})
   485  
   486  	t.Run("scaling down, buffer policy", func(t *testing.T) {
   487  		t.Parallel()
   488  		c, m := newFakeController()
   489  		fas, f := defaultFixtures()
   490  		fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(8)
   491  
   492  		f.Spec.Replicas = 20
   493  		f.Status.Replicas = 20
   494  		f.Status.AllocatedReplicas = 5
   495  		f.Status.ReadyReplicas = 15
   496  
   497  		fUpdated := false
   498  		fasUpdated := false
   499  
   500  		m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   501  			return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
   502  		})
   503  
   504  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   505  			fasUpdated = true
   506  			ca := action.(k8stesting.UpdateAction)
   507  			fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler)
   508  			assert.Equal(t, fas.Status.AbleToScale, true)
   509  			assert.Equal(t, fas.Status.ScalingLimited, false)
   510  			assert.Equal(t, fas.Status.CurrentReplicas, int32(20))
   511  			assert.Equal(t, fas.Status.DesiredReplicas, int32(13))
   512  			assert.Equal(t, fas.Status.LastAppliedPolicy, autoscalingv1.BufferPolicyType)
   513  			assert.NotNil(t, fas.Status.LastScaleTime)
   514  			return true, fas, nil
   515  		})
   516  
   517  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   518  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   519  		})
   520  
   521  		m.AgonesClient.AddReactor("update", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) {
   522  			fUpdated = true
   523  			ca := action.(k8stesting.UpdateAction)
   524  			f := ca.GetObject().(*agonesv1.Fleet)
   525  			assert.Equal(t, f.Spec.Replicas, int32(13))
   526  
   527  			return true, f, nil
   528  		})
   529  
   530  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   531  		defer cancel()
   532  		fleetAutoscalerThreadEventually(t, c, fas)
   533  
   534  		err := c.syncFleetAutoscaler(ctx, "default/fas-1")
   535  		assert.Nil(t, err)
   536  		assert.True(t, fUpdated, "fleet should have been updated")
   537  		assert.True(t, fasUpdated, "fleetautoscaler should have been updated")
   538  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "AutoScalingFleet")
   539  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   540  	})
   541  
   542  	t.Run("no scaling down because fleet is marked for deletion, buffer policy", func(t *testing.T) {
   543  		t.Parallel()
   544  		c, m := newFakeController()
   545  		fas, f := defaultFixtures()
   546  		fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(8)
   547  
   548  		f.Spec.Replicas = 20
   549  		f.Status.Replicas = 20
   550  		f.Status.AllocatedReplicas = 5
   551  		f.Status.ReadyReplicas = 15
   552  		f.DeletionTimestamp = &metav1.Time{
   553  			Time: time.Now(),
   554  		}
   555  
   556  		m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   557  			return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
   558  		})
   559  
   560  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   561  			assert.FailNow(t, "fleetautoscaler should not update")
   562  			return true, nil, nil
   563  		})
   564  
   565  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   566  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   567  		})
   568  
   569  		m.AgonesClient.AddReactor("update", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   570  			assert.FailNow(t, "fleet should not update")
   571  
   572  			return false, nil, nil
   573  		})
   574  
   575  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   576  		defer cancel()
   577  		fleetAutoscalerThreadEventually(t, c, fas)
   578  
   579  		err := c.syncFleetAutoscaler(ctx, "default/fas-1")
   580  		assert.Nil(t, err)
   581  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   582  	})
   583  
   584  	t.Run("no scaling no update", func(t *testing.T) {
   585  		t.Parallel()
   586  		c, m := newFakeController()
   587  		fas, f := defaultFixtures()
   588  
   589  		f.Spec.Replicas = 10
   590  		f.Status.Replicas = 10
   591  		f.Status.ReadyReplicas = 5
   592  		fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(5)
   593  		fas.Status.CurrentReplicas = 10
   594  		fas.Status.DesiredReplicas = 10
   595  
   596  		m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   597  			return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
   598  		})
   599  
   600  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   601  			assert.FailNow(t, "fleetautoscaler should not update")
   602  			return false, nil, nil
   603  		})
   604  
   605  		m.AgonesClient.AddReactor("update", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   606  			assert.FailNow(t, "fleet should not update")
   607  			return false, nil, nil
   608  		})
   609  
   610  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   611  		defer cancel()
   612  		fleetAutoscalerThreadEventually(t, c, fas)
   613  
   614  		err := c.syncFleetAutoscaler(ctx, fas.ObjectMeta.Name)
   615  		assert.Nil(t, err)
   616  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   617  	})
   618  
   619  	t.Run("fleet not available", func(t *testing.T) {
   620  		t.Parallel()
   621  		c, m := newFakeController()
   622  		fas, _ := defaultFixtures()
   623  		fas.Status.DesiredReplicas = 10
   624  		fas.Status.CurrentReplicas = 5
   625  		updated := false
   626  
   627  		m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   628  			return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
   629  		})
   630  
   631  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   632  			updated = true
   633  			ca := action.(k8stesting.UpdateAction)
   634  			fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler)
   635  			assert.Equal(t, fas.Status.CurrentReplicas, int32(0))
   636  			assert.Equal(t, fas.Status.DesiredReplicas, int32(0))
   637  			return true, fas, nil
   638  		})
   639  
   640  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   641  		defer cancel()
   642  		fleetAutoscalerThreadEventually(t, c, fas)
   643  
   644  		err := c.syncFleetAutoscaler(ctx, "default/fas-1")
   645  		assert.Nil(t, err)
   646  		assert.True(t, updated)
   647  
   648  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "FailedGetFleet")
   649  	})
   650  
   651  	t.Run("fleet not available, error on status update", func(t *testing.T) {
   652  		t.Parallel()
   653  		c, m := newFakeController()
   654  		fas, _ := defaultFixtures()
   655  		fas.Status.DesiredReplicas = 10
   656  		fas.Status.CurrentReplicas = 5
   657  
   658  		m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   659  			return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
   660  		})
   661  
   662  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   663  			ca := action.(k8stesting.UpdateAction)
   664  			fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler)
   665  			return true, fas, errors.New("random-err")
   666  		})
   667  
   668  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   669  		defer cancel()
   670  		fleetAutoscalerThreadEventually(t, c, fas)
   671  
   672  		err := c.syncFleetAutoscaler(ctx, "default/fas-1")
   673  		if assert.NotNil(t, err) {
   674  			assert.Equal(t, "error updating status for fleetautoscaler fas-1: random-err", err.Error())
   675  		}
   676  
   677  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "FailedGetFleet")
   678  	})
   679  
   680  	t.Run("wrong policy", func(t *testing.T) {
   681  		t.Parallel()
   682  		c, m := newFakeController()
   683  		fas, f := defaultFixtures()
   684  
   685  		// wrong policy, should fail
   686  		fas.Spec.Policy = autoscalingv1.FleetAutoscalerPolicy{
   687  			Type: "WRONG TYPE",
   688  		}
   689  
   690  		m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   691  			return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
   692  		})
   693  
   694  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   695  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   696  		})
   697  
   698  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   699  		defer cancel()
   700  		fleetAutoscalerThreadEventually(t, c, fas)
   701  
   702  		err := c.syncFleetAutoscaler(ctx, "default/fas-1")
   703  		if assert.NotNil(t, err) {
   704  			assert.Equal(t, "error calculating autoscaling fleet: fleet-1: wrong policy type, should be one of: Buffer, Webhook, Counter, List, Schedule, Chain", err.Error())
   705  		}
   706  	})
   707  
   708  	t.Run("wrong policy, error on status update", func(t *testing.T) {
   709  		t.Parallel()
   710  		c, m := newFakeController()
   711  		fas, f := defaultFixtures()
   712  		fas.Status.DesiredReplicas = 10
   713  		// wrong policy, should fail
   714  		fas.Spec.Policy = autoscalingv1.FleetAutoscalerPolicy{
   715  			Type: "WRONG TYPE",
   716  		}
   717  
   718  		m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   719  			return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
   720  		})
   721  
   722  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   723  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   724  		})
   725  
   726  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   727  			ca := action.(k8stesting.UpdateAction)
   728  			fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler)
   729  			return true, fas, errors.New("random-err")
   730  		})
   731  
   732  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   733  		defer cancel()
   734  		fleetAutoscalerThreadEventually(t, c, fas)
   735  
   736  		err := c.syncFleetAutoscaler(ctx, "default/fas-1")
   737  		if assert.NotNil(t, err) {
   738  			assert.Equal(t, "error updating status for fleetautoscaler fas-1: random-err", err.Error())
   739  		}
   740  	})
   741  
   742  	t.Run("error on scale fleet step", func(t *testing.T) {
   743  		t.Parallel()
   744  		c, m := newFakeController()
   745  		fas, f := defaultFixtures()
   746  
   747  		m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   748  			return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
   749  		})
   750  
   751  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   752  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   753  		})
   754  
   755  		m.AgonesClient.AddReactor("update", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) {
   756  			ca := action.(k8stesting.UpdateAction)
   757  			return true, ca.GetObject().(*agonesv1.Fleet), errors.New("random-err")
   758  		})
   759  
   760  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   761  		defer cancel()
   762  		fleetAutoscalerThreadEventually(t, c, fas)
   763  
   764  		err := c.syncFleetAutoscaler(ctx, "default/fas-1")
   765  		if assert.NotNil(t, err) {
   766  			assert.Equal(t, "error autoscaling fleet fleet-1 to 7 replicas: error updating replicas for fleet fleet-1: random-err", err.Error())
   767  		}
   768  
   769  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "AutoScalingFleetError")
   770  	})
   771  
   772  	t.Run("Missing fleet autoscaler, doesn't fail/panic", func(t *testing.T) {
   773  		t.Parallel()
   774  
   775  		c, m := newFakeController()
   776  		m.AgonesClient.AddReactor("get", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   777  			ga := action.(k8stesting.GetAction)
   778  			return true, nil, k8serrors.NewNotFound(corev1.Resource("gameserver"), ga.GetName())
   779  		})
   780  
   781  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced)
   782  		defer cancel()
   783  
   784  		require.NoError(t, c.syncFleetAutoscaler(ctx, "default/fas-1"))
   785  	})
   786  }
   787  
   788  // fleetAutoscalerThreadEventually waits to see if a thread is started for the given fleet autoscaler
   789  // this is a sign that the informer has been started and the fleet autoscaler has been processed
   790  // by the informer.
   791  func fleetAutoscalerThreadEventually(t *testing.T, c *Controller, fas *autoscalingv1.FleetAutoscaler) {
   792  	require.Eventually(t, func() bool {
   793  		c.fasThreadMutex.Lock()
   794  		defer c.fasThreadMutex.Unlock()
   795  		_, ok := c.fasThreads[fas.ObjectMeta.UID]
   796  		return ok
   797  	}, 5*time.Second, 100*time.Millisecond, "fleet autoscaler controller didn't start the sync thread")
   798  }
   799  
   800  func TestControllerScaleFleet(t *testing.T) {
   801  	t.Parallel()
   802  
   803  	t.Run("fleet that must be scaled", func(t *testing.T) {
   804  		c, m := newFakeController()
   805  		fas, f := defaultFixtures()
   806  		replicas := f.Spec.Replicas + 5
   807  
   808  		update := false
   809  
   810  		m.AgonesClient.AddReactor("update", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) {
   811  			update = true
   812  			ca := action.(k8stesting.UpdateAction)
   813  			f := ca.GetObject().(*agonesv1.Fleet)
   814  			assert.Equal(t, replicas, f.Spec.Replicas)
   815  
   816  			return true, f, nil
   817  		})
   818  
   819  		err := c.scaleFleet(context.Background(), fas, f, replicas)
   820  		assert.Nil(t, err)
   821  		assert.True(t, update, "Fleet should be updated")
   822  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingFleet")
   823  	})
   824  
   825  	t.Run("error on updating fleet", func(t *testing.T) {
   826  		c, m := newFakeController()
   827  		fas, f := defaultFixtures()
   828  		replicas := f.Spec.Replicas + 5
   829  
   830  		m.AgonesClient.AddReactor("update", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) {
   831  			ca := action.(k8stesting.UpdateAction)
   832  			return true, ca.GetObject().(*agonesv1.Fleet), errors.New("random-err")
   833  		})
   834  
   835  		err := c.scaleFleet(context.Background(), fas, f, replicas)
   836  		if assert.NotNil(t, err) {
   837  			assert.Equal(t, "error updating replicas for fleet fleet-1: random-err", err.Error())
   838  		}
   839  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "AutoScalingFleetError")
   840  	})
   841  
   842  	t.Run("equal replicas, no update", func(t *testing.T) {
   843  		c, m := newFakeController()
   844  		fas, f := defaultFixtures()
   845  		replicas := f.Spec.Replicas
   846  
   847  		m.AgonesClient.AddReactor("update", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   848  			assert.FailNow(t, "fleet should not update")
   849  			return false, nil, nil
   850  		})
   851  
   852  		err := c.scaleFleet(context.Background(), fas, f, replicas)
   853  		assert.Nil(t, err)
   854  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   855  	})
   856  }
   857  
   858  func TestControllerUpdateStatus(t *testing.T) {
   859  	t.Parallel()
   860  
   861  	t.Run("must update", func(t *testing.T) {
   862  		c, m := newFakeController()
   863  		fas, _ := defaultFixtures()
   864  
   865  		fasUpdated := false
   866  
   867  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   868  			fasUpdated = true
   869  			ca := action.(k8stesting.UpdateAction)
   870  			fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler)
   871  			assert.Equal(t, fas.Status.AbleToScale, true)
   872  			assert.Equal(t, fas.Status.ScalingLimited, false)
   873  			assert.Equal(t, fas.Status.CurrentReplicas, int32(10))
   874  			assert.Equal(t, fas.Status.DesiredReplicas, int32(20))
   875  			assert.Equal(t, fas.Status.LastAppliedPolicy, autoscalingv1.BufferPolicyType)
   876  			assert.NotNil(t, fas.Status.LastScaleTime)
   877  			return true, fas, nil
   878  		})
   879  
   880  		ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced)
   881  		defer cancel()
   882  
   883  		err := c.updateStatus(ctx, fas, 10, 20, true, false, fas.Spec.Policy.Type)
   884  		assert.Nil(t, err)
   885  		assert.True(t, fasUpdated)
   886  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   887  	})
   888  
   889  	t.Run("must not update", func(t *testing.T) {
   890  		c, m := newFakeController()
   891  		fas, _ := defaultFixtures()
   892  
   893  		fas.Status.AbleToScale = true
   894  		fas.Status.ScalingLimited = false
   895  		fas.Status.CurrentReplicas = 10
   896  		fas.Status.DesiredReplicas = 20
   897  		fas.Status.LastScaleTime = nil
   898  		fas.Status.LastAppliedPolicy = fas.Spec.Policy.Type
   899  
   900  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   901  			assert.FailNow(t, "should not update")
   902  			return false, nil, nil
   903  		})
   904  
   905  		ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced)
   906  		defer cancel()
   907  
   908  		err := c.updateStatus(ctx, fas, fas.Status.CurrentReplicas, fas.Status.DesiredReplicas, false, fas.Status.ScalingLimited, fas.Spec.Policy.Type)
   909  		assert.Nil(t, err)
   910  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   911  	})
   912  
   913  	t.Run("update with error", func(t *testing.T) {
   914  		c, m := newFakeController()
   915  		fas, _ := defaultFixtures()
   916  
   917  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   918  			return true, nil, errors.New("random-err")
   919  		})
   920  
   921  		ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced)
   922  		defer cancel()
   923  
   924  		err := c.updateStatus(ctx, fas, fas.Status.CurrentReplicas, fas.Status.DesiredReplicas, false, fas.Status.ScalingLimited, fas.Spec.Policy.Type)
   925  		if assert.NotNil(t, err) {
   926  			assert.Equal(t, "error updating status for fleetautoscaler fas-1: random-err", err.Error())
   927  		}
   928  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   929  	})
   930  
   931  	t.Run("update with a scaling limit", func(t *testing.T) {
   932  		c, m := newFakeController()
   933  		fas, _ := defaultFixtures()
   934  
   935  		err := c.updateStatus(context.Background(), fas, 10, 20, true, true, fas.Spec.Policy.Type)
   936  		assert.Nil(t, err)
   937  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingLimited")
   938  	})
   939  
   940  	t.Run("update with a scaling limit with minimum", func(t *testing.T) {
   941  		c, m := newFakeController()
   942  		fas, _ := defaultFixtures()
   943  
   944  		err := c.updateStatus(context.Background(), fas, 1, 3, true, true, fas.Spec.Policy.Type)
   945  		assert.Nil(t, err)
   946  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "limited to minimum size of 3")
   947  	})
   948  
   949  	t.Run("update with a scaling limit with maximum", func(t *testing.T) {
   950  		c, m := newFakeController()
   951  		fas, _ := defaultFixtures()
   952  
   953  		err := c.updateStatus(context.Background(), fas, 12, 10, true, true, fas.Spec.Policy.Type)
   954  		assert.Nil(t, err)
   955  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "limited to maximum size of 10")
   956  	})
   957  }
   958  
   959  func TestControllerUpdateStatusUnableToScale(t *testing.T) {
   960  	t.Parallel()
   961  
   962  	t.Run("must update", func(t *testing.T) {
   963  		c, m := newFakeController()
   964  		fas, _ := defaultFixtures()
   965  		fas.Status.DesiredReplicas = 10
   966  
   967  		fasUpdated := false
   968  
   969  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   970  			fasUpdated = true
   971  			ca := action.(k8stesting.UpdateAction)
   972  			fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler)
   973  			assert.Equal(t, fas.Status.AbleToScale, false)
   974  			assert.Equal(t, fas.Status.ScalingLimited, false)
   975  			assert.Equal(t, fas.Status.CurrentReplicas, int32(0))
   976  			assert.Equal(t, fas.Status.DesiredReplicas, int32(0))
   977  			assert.Equal(t, fas.Status.LastAppliedPolicy, autoscalingv1.FleetAutoscalerPolicyType(""))
   978  			assert.Nil(t, fas.Status.LastScaleTime)
   979  			return true, fas, nil
   980  		})
   981  
   982  		ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced)
   983  		defer cancel()
   984  
   985  		err := c.updateStatusUnableToScale(ctx, fas)
   986  		assert.Nil(t, err)
   987  		assert.True(t, fasUpdated)
   988  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   989  	})
   990  
   991  	t.Run("update with error", func(t *testing.T) {
   992  		c, m := newFakeController()
   993  		fas, _ := defaultFixtures()
   994  		fas.Status.DesiredReplicas = 10
   995  
   996  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   997  			ca := action.(k8stesting.UpdateAction)
   998  			fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler)
   999  			return true, fas, errors.New("random-err")
  1000  		})
  1001  
  1002  		ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced)
  1003  		defer cancel()
  1004  
  1005  		err := c.updateStatusUnableToScale(ctx, fas)
  1006  		if assert.NotNil(t, err) {
  1007  			assert.Equal(t, "error updating status for fleetautoscaler fas-1: random-err", err.Error())
  1008  		}
  1009  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
  1010  	})
  1011  
  1012  	t.Run("must not update", func(t *testing.T) {
  1013  		c, m := newFakeController()
  1014  		fas, _ := defaultFixtures()
  1015  		fas.Status.AbleToScale = false
  1016  		fas.Status.ScalingLimited = false
  1017  		fas.Status.CurrentReplicas = 0
  1018  		fas.Status.DesiredReplicas = 0
  1019  		fas.Status.LastAppliedPolicy = autoscalingv1.FleetAutoscalerPolicyType("")
  1020  
  1021  		m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1022  			assert.FailNow(t, "fleetautoscaler should not update")
  1023  			return false, nil, nil
  1024  		})
  1025  
  1026  		ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced)
  1027  		defer cancel()
  1028  
  1029  		err := c.updateStatusUnableToScale(ctx, fas)
  1030  		assert.Nil(t, err)
  1031  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
  1032  	})
  1033  }
  1034  
  1035  func TestControllerEvents(t *testing.T) {
  1036  	t.Parallel()
  1037  
  1038  	c, mocks := newFakeController()
  1039  	fakeWatch := watch.NewFake()
  1040  	mocks.AgonesClient.AddWatchReactor("fleetautoscalers", k8stesting.DefaultWatchReactor(fakeWatch, nil))
  1041  	_, cancel := agtesting.StartInformers(mocks, c.fleetAutoscalerSynced)
  1042  	defer cancel()
  1043  
  1044  	// add fleet autoscaler
  1045  	fas, _ := defaultFixtures()
  1046  	fakeWatch.Add(fas.DeepCopy())
  1047  
  1048  	require.Eventually(t, func() bool {
  1049  		c.fasThreadMutex.Lock()
  1050  		defer c.fasThreadMutex.Unlock()
  1051  		return len(c.fasThreads) == 1
  1052  	}, 30*time.Second, time.Second, "should be added")
  1053  
  1054  	c.fasThreadMutex.Lock()
  1055  	require.Equal(t, fas.ObjectMeta.Generation, c.fasThreads[fas.ObjectMeta.UID].generation)
  1056  	c.fasThreadMutex.Unlock()
  1057  
  1058  	// modify the fleet autoscaler
  1059  	fas.ObjectMeta.Generation++
  1060  	fakeWatch.Modify(fas.DeepCopy())
  1061  
  1062  	require.Eventually(t, func() bool {
  1063  		c.fasThreadMutex.Lock()
  1064  		defer c.fasThreadMutex.Unlock()
  1065  		return fas.ObjectMeta.Generation == c.fasThreads[fas.ObjectMeta.UID].generation
  1066  	}, 30*time.Second, time.Second, "should be updated")
  1067  
  1068  	// delete the fleet auto scaler
  1069  	fakeWatch.Delete(fas.DeepCopy())
  1070  	require.Eventually(t, func() bool {
  1071  		c.fasThreadMutex.Lock()
  1072  		defer c.fasThreadMutex.Unlock()
  1073  		return len(c.fasThreads) == 0
  1074  	}, 30*time.Second, time.Second, "should be deleted")
  1075  }
  1076  
  1077  func TestControllerAddUpdateDeleteFasThread(t *testing.T) {
  1078  	t.Parallel()
  1079  
  1080  	var counter int64
  1081  	c, m := newFakeController()
  1082  	c.workerqueue.SyncHandler = func(_ context.Context, _ string) error {
  1083  		atomic.AddInt64(&counter, 1)
  1084  		return nil
  1085  	}
  1086  
  1087  	m.ExtClient.AddReactor("get", "customresourcedefinitions", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1088  		return true, agtesting.NewEstablishedCRD(), nil
  1089  	})
  1090  
  1091  	ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced)
  1092  	defer cancel()
  1093  	go func() {
  1094  		require.NoError(t, c.Run(ctx, 1))
  1095  	}()
  1096  
  1097  	fas, _ := defaultFixtures()
  1098  	fas.Spec.Sync.FixedInterval.Seconds = 1
  1099  
  1100  	c.addFasThread(fas, true)
  1101  
  1102  	// unfortunately we can't mock the timer, so we'll confirm that two enqueue processes fire. One on method execution,
  1103  	// and then one based on the ticker.
  1104  	require.Eventuallyf(t, func() bool {
  1105  		return atomic.LoadInt64(&counter) >= 2
  1106  	}, 10*time.Second, time.Second, "Should have at least two counters")
  1107  
  1108  	c.fasThreadMutex.Lock()
  1109  	require.Len(t, c.fasThreads, 1)
  1110  	c.fasThreadMutex.Unlock()
  1111  
  1112  	// update with the same values
  1113  	c.updateFasThread(ctx, fas)
  1114  	c.fasThreadMutex.Lock()
  1115  	require.Len(t, c.fasThreads, 1)
  1116  	require.Equal(t, fas.ObjectMeta.Generation, c.fasThreads[fas.ObjectMeta.UID].generation)
  1117  	c.fasThreadMutex.Unlock()
  1118  
  1119  	// update duration
  1120  	fas.Spec.Sync.FixedInterval.Seconds = 3
  1121  	fas.ObjectMeta.Generation++
  1122  
  1123  	c.updateFasThread(ctx, fas)
  1124  	c.fasThreadMutex.Lock()
  1125  	require.Len(t, c.fasThreads, 1)
  1126  	require.Equal(t, fas.ObjectMeta.Generation, c.fasThreads[fas.ObjectMeta.UID].generation)
  1127  	c.fasThreadMutex.Unlock()
  1128  
  1129  	// update, but with a second fas, that doesn't exist in the system yet
  1130  	fas2 := fas.DeepCopy()
  1131  	fas2.Spec.Sync.FixedInterval.Seconds = 1
  1132  	fas2.ObjectMeta.Generation = 5
  1133  	fas2.ObjectMeta.UID = "4321"
  1134  
  1135  	c.updateFasThread(ctx, fas2)
  1136  	c.fasThreadMutex.Lock()
  1137  	require.Len(t, c.fasThreads, 2)
  1138  	require.Equal(t, fas.ObjectMeta.Generation, c.fasThreads[fas.ObjectMeta.UID].generation)
  1139  	require.Equal(t, fas2.ObjectMeta.Generation, c.fasThreads[fas2.ObjectMeta.UID].generation)
  1140  	c.fasThreadMutex.Unlock()
  1141  
  1142  	// delete the current fas.
  1143  	c.deleteFasThread(ctx, fas, true)
  1144  	c.fasThreadMutex.Lock()
  1145  	require.Len(t, c.fasThreads, 1)
  1146  	require.Equal(t, fas2.ObjectMeta.Generation, c.fasThreads[fas2.ObjectMeta.UID].generation)
  1147  	c.fasThreadMutex.Unlock()
  1148  
  1149  	c.deleteFasThread(ctx, fas2, true)
  1150  	c.fasThreadMutex.Lock()
  1151  	require.Len(t, c.fasThreads, 0)
  1152  	c.fasThreadMutex.Unlock()
  1153  
  1154  	// we shouldn't get any more updates, so wait for 3 checks in a row that have the
  1155  	// same counter amount to prove that there aren't any changes for a while.
  1156  	var check []int64
  1157  	require.Eventually(t, func() bool {
  1158  		check = append(check, atomic.LoadInt64(&counter))
  1159  		l := len(check)
  1160  		if l < 3 {
  1161  			return false
  1162  		}
  1163  		l--
  1164  		return check[l] == check[l-1] && check[l-1] == check[l-2]
  1165  	}, 30*time.Second, 2*time.Second, "changes keep happening", check)
  1166  }
  1167  
  1168  func TestControllerCleanFasThreads(t *testing.T) {
  1169  	c, m := newFakeController()
  1170  	fas, _ := defaultFixtures()
  1171  
  1172  	m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1173  		return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil
  1174  	})
  1175  
  1176  	_, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced)
  1177  	defer cancel()
  1178  
  1179  	c.fasThreadMutex.Lock()
  1180  	c.fasThreads = map[types.UID]fasThread{
  1181  		"1":                {func() {}, fasState{}, 1},
  1182  		"2":                {func() {}, fasState{}, 2},
  1183  		fas.ObjectMeta.UID: {func() {}, fasState{}, 3},
  1184  	}
  1185  	c.fasThreadMutex.Unlock()
  1186  
  1187  	key, err := cache.MetaNamespaceKeyFunc(fas)
  1188  	require.NoError(t, err)
  1189  	require.NoError(t, c.cleanFasThreads(context.Background(), key))
  1190  
  1191  	require.Len(t, c.fasThreads, 1)
  1192  	require.Equal(t, int64(3), c.fasThreads[fas.ObjectMeta.UID].generation)
  1193  }
  1194  
  1195  func defaultFixtures() (*autoscalingv1.FleetAutoscaler, *agonesv1.Fleet) {
  1196  	f := &agonesv1.Fleet{
  1197  		ObjectMeta: metav1.ObjectMeta{
  1198  			Name:      "fleet-1",
  1199  			Namespace: "default",
  1200  			UID:       "1234",
  1201  		},
  1202  		Spec: agonesv1.FleetSpec{
  1203  			Replicas:   8,
  1204  			Scheduling: apis.Packed,
  1205  			Template:   agonesv1.GameServerTemplateSpec{},
  1206  		},
  1207  		Status: agonesv1.FleetStatus{
  1208  			Replicas:          5,
  1209  			ReadyReplicas:     3,
  1210  			ReservedReplicas:  3,
  1211  			AllocatedReplicas: 2,
  1212  		},
  1213  	}
  1214  
  1215  	fas := &autoscalingv1.FleetAutoscaler{
  1216  		ObjectMeta: metav1.ObjectMeta{
  1217  			Name:       "fas-1",
  1218  			Namespace:  "default",
  1219  			Generation: 2,
  1220  			UID:        "4567",
  1221  		},
  1222  		Spec: autoscalingv1.FleetAutoscalerSpec{
  1223  			FleetName: f.ObjectMeta.Name,
  1224  			Policy: autoscalingv1.FleetAutoscalerPolicy{
  1225  				Type: autoscalingv1.BufferPolicyType,
  1226  				Buffer: &autoscalingv1.BufferPolicy{
  1227  					BufferSize:  intstr.FromInt(5),
  1228  					MaxReplicas: 100,
  1229  				},
  1230  			},
  1231  			Sync: &autoscalingv1.FleetAutoscalerSync{
  1232  				Type: autoscalingv1.FixedIntervalSyncType,
  1233  				FixedInterval: autoscalingv1.FixedIntervalSync{
  1234  					Seconds: 30,
  1235  				},
  1236  			},
  1237  		},
  1238  	}
  1239  
  1240  	return fas, f
  1241  }
  1242  
  1243  func defaultWebhookFixtures() (*autoscalingv1.FleetAutoscaler, *agonesv1.Fleet) {
  1244  	fas, f := defaultFixtures()
  1245  	fas.Spec.Policy.Type = autoscalingv1.WebhookPolicyType
  1246  	fas.Spec.Policy.Buffer = nil
  1247  	url := "/autoscaler"
  1248  	fas.Spec.Policy.Webhook = &autoscalingv1.URLConfiguration{
  1249  		Service: &admregv1.ServiceReference{
  1250  			Name: "fleetautoscaler-service",
  1251  			Path: &url,
  1252  		},
  1253  	}
  1254  
  1255  	return fas, f
  1256  }
  1257  
  1258  // newFakeController returns a controller, backed by the fake Clientset
  1259  func newFakeController() (*Controller, agtesting.Mocks) {
  1260  	m := agtesting.NewMocks()
  1261  	counter := gameservers.NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory)
  1262  	c := NewController(healthcheck.NewHandler(), m.KubeClient, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory, counter)
  1263  	c.recorder = m.FakeRecorder
  1264  	return c, m
  1265  }
  1266  
  1267  // newFakeExtensions returns a fake extensions struct
  1268  func newFakeExtensions() *Extensions {
  1269  	return NewExtensions(webhooks.NewWebHook(http.NewServeMux()))
  1270  }
  1271  
  1272  func newAdmissionReview(fas autoscalingv1.FleetAutoscaler) (admissionv1.AdmissionReview, error) {
  1273  	raw, err := json.Marshal(fas)
  1274  	if err != nil {
  1275  		return admissionv1.AdmissionReview{}, err
  1276  	}
  1277  	review := admissionv1.AdmissionReview{
  1278  		Request: &admissionv1.AdmissionRequest{
  1279  			Kind:      gvk,
  1280  			Operation: admissionv1.Create,
  1281  			Object: runtime.RawExtension{
  1282  				Raw: raw,
  1283  			},
  1284  			Namespace: "default",
  1285  		},
  1286  		Response: &admissionv1.AdmissionResponse{Allowed: true},
  1287  	}
  1288  	return review, err
  1289  }
  1290  
  1291  func newInvalidAdmissionReview() (admissionv1.AdmissionReview, error) {
  1292  	raw, err := json.Marshal([]byte(`1`))
  1293  	if err != nil {
  1294  		return admissionv1.AdmissionReview{}, err
  1295  	}
  1296  	review := admissionv1.AdmissionReview{
  1297  		Request: &admissionv1.AdmissionRequest{
  1298  			Kind:      gvk,
  1299  			Operation: admissionv1.Create,
  1300  			Object: runtime.RawExtension{
  1301  				Raw: raw,
  1302  			},
  1303  			Namespace: "default",
  1304  		},
  1305  		Response: &admissionv1.AdmissionResponse{Allowed: true},
  1306  	}
  1307  	return review, nil
  1308  }