k8s.io/apiserver@v0.31.1/pkg/util/flowcontrol/fairqueuing/queueset/fifo_list_test.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     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 queueset
    18  
    19  import (
    20  	"math/rand"
    21  	"reflect"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	fcrequest "k8s.io/apiserver/pkg/util/flowcontrol/request"
    27  )
    28  
    29  func TestFIFOWithEnqueueDequeueSingleRequest(t *testing.T) {
    30  	req := &request{}
    31  
    32  	list := newRequestFIFO()
    33  	list.Enqueue(req)
    34  
    35  	reqGot, ok := list.Dequeue()
    36  	if !ok {
    37  		t.Errorf("Expected true, but got: %t", ok)
    38  	}
    39  	if req != reqGot {
    40  		t.Errorf("Expected dequued request: (%p), but got: (%p)", req, reqGot)
    41  	}
    42  	if list.Length() != 0 {
    43  		t.Errorf("Expected length: %d, but got: %d)", 0, list.Length())
    44  	}
    45  }
    46  
    47  func TestFIFOWithEnqueueDequeueMultipleRequests(t *testing.T) {
    48  	arrival := []*request{{}, {}, {}, {}, {}, {}}
    49  
    50  	list := newRequestFIFO()
    51  	for i := range arrival {
    52  		list.Enqueue(arrival[i])
    53  	}
    54  
    55  	dequeued := make([]*request, 0)
    56  	for list.Length() > 0 {
    57  		req, _ := list.Dequeue()
    58  		dequeued = append(dequeued, req)
    59  	}
    60  
    61  	verifyOrder(t, arrival, dequeued)
    62  }
    63  
    64  func TestFIFOWithEnqueueDequeueSomeRequestsRemainInQueue(t *testing.T) {
    65  	list := newRequestFIFO()
    66  
    67  	arrival := []*request{{}, {}, {}, {}, {}, {}}
    68  	half := len(arrival) / 2
    69  	for i := range arrival {
    70  		list.Enqueue(arrival[i])
    71  	}
    72  
    73  	dequeued := make([]*request, 0)
    74  	for i := 0; i < half; i++ {
    75  		req, _ := list.Dequeue()
    76  		dequeued = append(dequeued, req)
    77  	}
    78  
    79  	verifyOrder(t, arrival[:half], dequeued)
    80  }
    81  
    82  func TestFIFOWithRemoveMultipleRequestsInArrivalOrder(t *testing.T) {
    83  	list := newRequestFIFO()
    84  
    85  	arrival := []*request{{}, {}, {}, {}, {}, {}}
    86  	removeFn := make([]removeFromFIFOFunc, 0)
    87  	for i := range arrival {
    88  		removeFn = append(removeFn, list.Enqueue(arrival[i]))
    89  	}
    90  
    91  	expected := append([]*request{}, arrival...)
    92  	for idx, f := range removeFn {
    93  		if a := f(); a != arrival[idx] {
    94  			t.Errorf("Removal %d returned %v instead of expected pointer", idx, a)
    95  		}
    96  		if a := f(); a != nil {
    97  			t.Errorf("Redundant removal %d returned %v instead of expected nil", idx, a)
    98  		}
    99  		expected = expected[1:]
   100  		actual := walkAll(list)
   101  		verifyOrder(t, expected, actual)
   102  	}
   103  
   104  	if list.Length() != 0 {
   105  		t.Errorf("Expected length: %d, but got: %d)", 0, list.Length())
   106  	}
   107  }
   108  
   109  func TestFIFORemoveFromFIFOFunc(t *testing.T) {
   110  	list := newRequestFIFO()
   111  	reqWant := &request{}
   112  	removeFn := list.Enqueue(reqWant)
   113  
   114  	reqGot := removeFn()
   115  	if reqWant != reqGot {
   116  		t.Errorf("Expected request identity: %p, but got: %p)", reqWant, reqGot)
   117  	}
   118  
   119  	if got := removeFn(); got != nil {
   120  		t.Errorf("Expected a nil request, but got: %v)", got)
   121  	}
   122  }
   123  
   124  func TestFIFOWithRemoveMultipleRequestsInRandomOrder(t *testing.T) {
   125  	list := newRequestFIFO()
   126  
   127  	arrival := []*request{{}, {}, {}, {}, {}, {}}
   128  	removeFn := make([]removeFromFIFOFunc, 0)
   129  	for i := range arrival {
   130  		removeFn = append(removeFn, list.Enqueue(arrival[i]))
   131  	}
   132  
   133  	expected := append([]*request{}, arrival...)
   134  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   135  	for range arrival {
   136  		idx := r.Intn(len(expected))
   137  		t.Logf("Removing random index %d", idx)
   138  		if e, a := expected[idx], removeFn[idx](); e != a {
   139  			t.Errorf("Removal of %d returned %v instead of expected pointer %v", idx, a, e)
   140  		}
   141  		if e, a := (*request)(nil), removeFn[idx](); e != a {
   142  			t.Errorf("Redundant removal of %d returned %v instead of expected nil pointer", idx, a)
   143  		}
   144  		expected = append(expected[:idx], expected[idx+1:]...)
   145  		actual := walkAll(list)
   146  		verifyOrder(t, expected, actual)
   147  		removeFn = append(removeFn[:idx], removeFn[idx+1:]...)
   148  	}
   149  	if list.Length() != 0 {
   150  		t.Errorf("Expected length: %d, but got: %d)", 0, list.Length())
   151  	}
   152  }
   153  
   154  func TestFIFOWithRemoveIsIdempotent(t *testing.T) {
   155  	list := newRequestFIFO()
   156  
   157  	arrival := []*request{{}, {}, {}, {}}
   158  	removeFn := make([]removeFromFIFOFunc, 0)
   159  	for i := range arrival {
   160  		removeFn = append(removeFn, list.Enqueue(arrival[i]))
   161  	}
   162  
   163  	// pick one request to be removed at random
   164  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   165  	randomIndex := r.Intn(len(removeFn))
   166  	t.Logf("Random remove index: %d", randomIndex)
   167  
   168  	// remove the request from the fifo twice, we expect it to be idempotent
   169  	removeFn[randomIndex]()
   170  	removeFn[randomIndex]()
   171  
   172  	lengthExpected := len(arrival) - 1
   173  	if lengthExpected != list.Length() {
   174  		t.Errorf("Expected length: %d, but got: %d)", lengthExpected, list.Length())
   175  	}
   176  
   177  	orderExpected := append(arrival[0:randomIndex], arrival[randomIndex+1:]...)
   178  	remainingRequests := walkAll(list)
   179  	verifyOrder(t, orderExpected, remainingRequests)
   180  }
   181  
   182  func TestFIFOQueueWorkEstimate(t *testing.T) {
   183  	qs := &queueSet{estimatedServiceDuration: time.Second}
   184  	list := newRequestFIFO()
   185  
   186  	update := func(we *queueSum, req *request, multiplier int) {
   187  		we.InitialSeatsSum += multiplier * req.InitialSeats()
   188  		we.MaxSeatsSum += multiplier * req.MaxSeats()
   189  		we.TotalWorkSum += fcrequest.SeatSeconds(multiplier) * req.totalWork()
   190  	}
   191  
   192  	assert := func(t *testing.T, want, got *queueSum) {
   193  		if !reflect.DeepEqual(want, got) {
   194  			t.Errorf("Expected queue work estimate to match, diff: %s", cmp.Diff(want, got))
   195  		}
   196  	}
   197  
   198  	newRequest := func(initialSeats, finalSeats uint64, additionalLatency time.Duration) *request {
   199  		return &request{workEstimate: qs.completeWorkEstimate(&fcrequest.WorkEstimate{
   200  			InitialSeats:      initialSeats,
   201  			FinalSeats:        finalSeats,
   202  			AdditionalLatency: additionalLatency,
   203  		})}
   204  	}
   205  	arrival := []*request{
   206  		newRequest(1, 3, time.Second),
   207  		newRequest(2, 2, 2*time.Second),
   208  		newRequest(3, 1, 3*time.Second),
   209  	}
   210  	removeFn := make([]removeFromFIFOFunc, 0)
   211  
   212  	queueSumExpected := queueSum{}
   213  	for i := range arrival {
   214  		req := arrival[i]
   215  		removeFn = append(removeFn, list.Enqueue(req))
   216  		update(&queueSumExpected, req, 1)
   217  
   218  		workEstimateGot := list.QueueSum()
   219  		assert(t, &queueSumExpected, &workEstimateGot)
   220  	}
   221  
   222  	// NOTE: the test expects the request and the remove func to be at the same index
   223  	for i := range removeFn {
   224  		req := arrival[i]
   225  		removeFn[i]()
   226  
   227  		update(&queueSumExpected, req, -1)
   228  
   229  		workEstimateGot := list.QueueSum()
   230  		assert(t, &queueSumExpected, &workEstimateGot)
   231  
   232  		// check idempotency
   233  		removeFn[i]()
   234  
   235  		workEstimateGot = list.QueueSum()
   236  		assert(t, &queueSumExpected, &workEstimateGot)
   237  	}
   238  
   239  	// Check second type of idempotency: Dequeue + removeFn.
   240  	for i := range arrival {
   241  		req := arrival[i]
   242  		removeFn[i] = list.Enqueue(req)
   243  
   244  		update(&queueSumExpected, req, 1)
   245  	}
   246  
   247  	for i := range arrival {
   248  		// we expect Dequeue to pop the oldest request that should
   249  		// have the lowest index as well.
   250  		req := arrival[i]
   251  
   252  		if _, ok := list.Dequeue(); !ok {
   253  			t.Errorf("Unexpected failed dequeue: %d", i)
   254  		}
   255  
   256  		update(&queueSumExpected, req, -1)
   257  
   258  		queueSumGot := list.QueueSum()
   259  		assert(t, &queueSumExpected, &queueSumGot)
   260  
   261  		removeFn[i]()
   262  
   263  		queueSumGot = list.QueueSum()
   264  		assert(t, &queueSumExpected, &queueSumGot)
   265  	}
   266  }
   267  
   268  func TestFIFOWithWalk(t *testing.T) {
   269  	list := newRequestFIFO()
   270  
   271  	arrival := []*request{{}, {}, {}, {}, {}, {}}
   272  	for i := range arrival {
   273  		list.Enqueue(arrival[i])
   274  	}
   275  
   276  	visited := walkAll(list)
   277  
   278  	verifyOrder(t, arrival, visited)
   279  }
   280  
   281  func verifyOrder(t *testing.T, expected, actual []*request) {
   282  	if len(expected) != len(actual) {
   283  		t.Fatalf("Expected slice length: %d, but got: %d", len(expected), len(actual))
   284  	}
   285  	for i := range expected {
   286  		if expected[i] != actual[i] {
   287  			t.Errorf("Dequeue order mismatch, expected request: (%p), but got: (%p)", expected[i], actual[i])
   288  		}
   289  	}
   290  }
   291  
   292  func walkAll(l fifo) []*request {
   293  	visited := make([]*request, 0)
   294  	l.Walk(func(req *request) bool {
   295  		visited = append(visited, req)
   296  		return true
   297  	})
   298  
   299  	return visited
   300  }