agones.dev/agones@v1.54.0/pkg/metrics/util_test.go (about)

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