go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/appengine/tsmon/tasknum_test.go (about)

     1  // Copyright 2016 The LUCI Authors.
     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 tsmon
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"testing"
    21  	"time"
    22  
    23  	"go.chromium.org/luci/appengine/gaetesting"
    24  	"go.chromium.org/luci/common/clock/testclock"
    25  	"go.chromium.org/luci/common/tsmon"
    26  	"go.chromium.org/luci/common/tsmon/target"
    27  	"go.chromium.org/luci/gae/service/datastore"
    28  	srvtsmon "go.chromium.org/luci/server/tsmon"
    29  
    30  	. "github.com/smartystreets/goconvey/convey"
    31  )
    32  
    33  func TestFindGaps(t *testing.T) {
    34  	t.Parallel()
    35  
    36  	tests := []struct {
    37  		numbers        []int
    38  		wantFirst5Gaps []int
    39  	}{
    40  		{[]int{1}, []int{0, 2, 3, 4, 5}},
    41  		{[]int{-1, 1}, []int{0, 2, 3, 4, 5}},
    42  		{[]int{1, 3, 5}, []int{0, 2, 4, 6, 7}},
    43  		{[]int{5, 3, 1}, []int{0, 2, 4, 6, 7}},
    44  		{[]int{3, 1, 5}, []int{0, 2, 4, 6, 7}},
    45  		{[]int{4}, []int{0, 1, 2, 3, 5}},
    46  	}
    47  
    48  	for i, test := range tests {
    49  		Convey(fmt.Sprintf("%d. %v", i, test.numbers), t, func() {
    50  			numbers := map[int]struct{}{}
    51  			for _, n := range test.numbers {
    52  				numbers[n] = struct{}{}
    53  			}
    54  
    55  			nextNum := gapFinder(numbers)
    56  
    57  			// Read 5 numbers from the channel.
    58  			var got []int
    59  			for i := 0; i < 5; i++ {
    60  				got = append(got, nextNum())
    61  			}
    62  
    63  			So(got, ShouldResemble, test.wantFirst5Gaps)
    64  		})
    65  	}
    66  }
    67  
    68  func buildGAETestContext() (context.Context, testclock.TestClock) {
    69  	c := gaetesting.TestingContext()
    70  	c, clock := testclock.UseTime(c, testclock.TestTimeUTC)
    71  	datastore.GetTestable(c).Consistent(true)
    72  	c, _, _ = tsmon.WithFakes(c)
    73  	return c, clock
    74  }
    75  
    76  func TestAssignTaskNumbers(t *testing.T) {
    77  	t.Parallel()
    78  
    79  	task1 := target.Task{HostName: "1"}
    80  	task2 := target.Task{HostName: "2"}
    81  
    82  	allocator := DatastoreTaskNumAllocator{}
    83  
    84  	Convey("Assigns task numbers to unassigned instances", t, func() {
    85  		c, _ := buildGAETestContext()
    86  
    87  		// Two new processes belonging to different targets.
    88  		_, err := allocator.NotifyTaskIsAlive(c, &task1, "some.id")
    89  		So(err, ShouldEqual, srvtsmon.ErrNoTaskNumber)
    90  		_, err = allocator.NotifyTaskIsAlive(c, &task2, "some.id")
    91  		So(err, ShouldEqual, srvtsmon.ErrNoTaskNumber)
    92  
    93  		So(AssignTaskNumbers(c), ShouldBeNil)
    94  
    95  		// Both get 0, since they belong to different targets.
    96  		i, err := allocator.NotifyTaskIsAlive(c, &task1, "some.id")
    97  		So(err, ShouldBeNil)
    98  		So(i, ShouldEqual, 0)
    99  		i, err = allocator.NotifyTaskIsAlive(c, &task2, "some.id")
   100  		So(err, ShouldBeNil)
   101  		So(i, ShouldEqual, 0)
   102  
   103  		// Once numbers are assigned, AssignTaskNumbers is noop.
   104  		So(AssignTaskNumbers(c), ShouldBeNil)
   105  	})
   106  
   107  	Convey("Doesn't assign the same task number", t, func() {
   108  		c, _ := buildGAETestContext()
   109  
   110  		// Two processes belonging to a single target.
   111  		allocator.NotifyTaskIsAlive(c, &task1, "some.task.0")
   112  		allocator.NotifyTaskIsAlive(c, &task1, "some.task.1")
   113  
   114  		So(AssignTaskNumbers(c), ShouldBeNil)
   115  
   116  		// Get different task numbers.
   117  		i, err := allocator.NotifyTaskIsAlive(c, &task1, "some.task.0")
   118  		So(err, ShouldBeNil)
   119  		So(i, ShouldEqual, 0)
   120  		i, err = allocator.NotifyTaskIsAlive(c, &task1, "some.task.1")
   121  		So(err, ShouldBeNil)
   122  		So(i, ShouldEqual, 1)
   123  	})
   124  
   125  	Convey("Expires old instances", t, func() {
   126  		c, clock := buildGAETestContext()
   127  
   128  		for _, count := range []int{1, taskQueryBatchSize + 1} {
   129  			Convey(fmt.Sprintf("Count: %d", count), func() {
   130  				// Request a bunch of task numbers.
   131  				for i := 0; i < count; i++ {
   132  					_, err := allocator.NotifyTaskIsAlive(c, &task1, fmt.Sprintf("%d", i))
   133  					So(err, ShouldEqual, srvtsmon.ErrNoTaskNumber)
   134  				}
   135  
   136  				// Get all the numbers assigned.
   137  				So(AssignTaskNumbers(c), ShouldBeNil)
   138  
   139  				// Yep. Assigned.
   140  				numbers := map[int]struct{}{}
   141  				for i := 0; i < count; i++ {
   142  					num, err := allocator.NotifyTaskIsAlive(c, &task1, fmt.Sprintf("%d", i))
   143  					So(err, ShouldBeNil)
   144  					numbers[num] = struct{}{}
   145  				}
   146  				So(len(numbers), ShouldEqual, count)
   147  
   148  				// Move time to make number assignments expire.
   149  				clock.Add(instanceExpirationTimeout + time.Second)
   150  				So(AssignTaskNumbers(c), ShouldBeNil)
   151  
   152  				// Yep. Expired.
   153  				for i := 0; i < count; i++ {
   154  					_, err := allocator.NotifyTaskIsAlive(c, &task1, fmt.Sprintf("%d", i))
   155  					So(err, ShouldEqual, srvtsmon.ErrNoTaskNumber)
   156  				}
   157  			})
   158  		}
   159  	})
   160  }