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 }