go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/tasks/check_build_liveness_test.go (about) 1 // Copyright 2023 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 tasks 16 17 import ( 18 "context" 19 "testing" 20 "time" 21 22 "google.golang.org/protobuf/types/known/durationpb" 23 "google.golang.org/protobuf/types/known/timestamppb" 24 25 "go.chromium.org/luci/common/clock/testclock" 26 "go.chromium.org/luci/gae/filter/txndefer" 27 "go.chromium.org/luci/gae/impl/memory" 28 "go.chromium.org/luci/gae/service/datastore" 29 "go.chromium.org/luci/server/tq" 30 31 "go.chromium.org/luci/buildbucket/appengine/internal/metrics" 32 "go.chromium.org/luci/buildbucket/appengine/model" 33 taskdefs "go.chromium.org/luci/buildbucket/appengine/tasks/defs" 34 pb "go.chromium.org/luci/buildbucket/proto" 35 36 . "github.com/smartystreets/goconvey/convey" 37 . "go.chromium.org/luci/common/testing/assertions" 38 ) 39 40 func TestCheckLiveness(t *testing.T) { 41 t.Parallel() 42 43 Convey("CheckLiveness", t, func() { 44 ctx := txndefer.FilterRDS(memory.Use(context.Background())) 45 ctx = metrics.WithServiceInfo(ctx, "svc", "job", "ins") 46 datastore.GetTestable(ctx).AutoIndex(true) 47 datastore.GetTestable(ctx).Consistent(true) 48 ctx, sch := tq.TestingContext(ctx, nil) 49 baseTime := testclock.TestRecentTimeLocal 50 ctx, _ = testclock.UseTime(ctx, baseTime) 51 52 bld := &model.Build{ 53 Proto: &pb.Build{ 54 Id: 1, 55 Builder: &pb.BuilderID{ 56 Project: "project", 57 Bucket: "bucket", 58 Builder: "builder", 59 }, 60 Status: pb.Status_SCHEDULED, 61 CreateTime: timestamppb.New(baseTime), 62 UpdateTime: timestamppb.New(baseTime), 63 SchedulingTimeout: &durationpb.Duration{ 64 Seconds: 60, 65 }, 66 ExecutionTimeout: &durationpb.Duration{ 67 Seconds: 120, 68 }, 69 }, 70 } 71 bs := &model.BuildStatus{ 72 Build: datastore.MakeKey(ctx, "Build", 1), 73 Status: pb.Status_SCHEDULED, 74 } 75 76 So(datastore.Put(ctx, bld, bs), ShouldBeNil) 77 78 Convey("build not found", func() { 79 err := CheckLiveness(ctx, 999, 10) 80 So(err, ShouldErrLike, "failed to get build") 81 So(sch.Tasks(), ShouldBeEmpty) 82 }) 83 84 Convey("build is ended", func() { 85 bld.Proto.Status = pb.Status_SUCCESS 86 bs.Status = pb.Status_SUCCESS 87 So(datastore.Put(ctx, bld), ShouldBeNil) 88 89 err := CheckLiveness(ctx, 1, 10) 90 So(err, ShouldBeNil) 91 So(sch.Tasks(), ShouldBeEmpty) 92 }) 93 94 Convey("exceeds scheduling timeout", func() { 95 now := baseTime.Add(61 * time.Second) 96 ctx, _ = testclock.UseTime(ctx, now) 97 98 err := CheckLiveness(ctx, 1, 10) 99 So(err, ShouldBeNil) 100 So(datastore.Get(ctx, bld, bs), ShouldBeNil) 101 So(bld.Status, ShouldEqual, pb.Status_INFRA_FAILURE) 102 So(bs.Status, ShouldEqual, pb.Status_INFRA_FAILURE) 103 // FinalizeResultDB, ExportBigQuery, NotifyPubSub, NotifyPubSubGoProxy tasks. 104 So(sch.Tasks(), ShouldHaveLength, 4) 105 }) 106 107 Convey("exceeds heartbeat timeout", func() { 108 bld.Proto.Status = pb.Status_STARTED 109 bld.Proto.StartTime = timestamppb.New(baseTime) 110 bs.Status = pb.Status_STARTED 111 steps := &model.BuildSteps{ 112 ID: 1, 113 Build: datastore.KeyForObj(ctx, bld), 114 } 115 So(steps.FromProto([]*pb.Step{ 116 {Name: "step1", Status: pb.Status_STARTED}, 117 }), ShouldBeNil) 118 So(datastore.Put(ctx, bld, steps), ShouldBeNil) 119 now := baseTime.Add(11 * time.Second) 120 ctx, _ = testclock.UseTime(ctx, now) 121 122 err := CheckLiveness(ctx, 1, 10) 123 So(err, ShouldBeNil) 124 So(datastore.Get(ctx, bld, bs, steps), ShouldBeNil) 125 So(bld.Status, ShouldEqual, pb.Status_INFRA_FAILURE) 126 So(bs.Status, ShouldEqual, pb.Status_INFRA_FAILURE) 127 mSteps, err := steps.ToProto(ctx) 128 So(err, ShouldBeNil) 129 So(mSteps, ShouldResembleProto, []*pb.Step{ 130 { 131 Name: "step1", 132 Status: pb.Status_CANCELED, 133 EndTime: timestamppb.New(now), 134 }, 135 }) 136 So(sch.Tasks(), ShouldHaveLength, 4) 137 }) 138 139 Convey("exceeds execution timeout", func() { 140 now := baseTime.Add(121 * time.Second) 141 bld.Proto.Status = pb.Status_STARTED 142 bld.Proto.StartTime = timestamppb.New(baseTime) 143 // not exceed heartbeat timeout 144 bld.Proto.UpdateTime = timestamppb.New(now.Add(-9 * time.Second)) 145 bs.Status = pb.Status_STARTED 146 147 So(datastore.Put(ctx, bld), ShouldBeNil) 148 ctx, _ = testclock.UseTime(ctx, now) 149 150 err := CheckLiveness(ctx, 1, 10) 151 So(err, ShouldBeNil) 152 So(datastore.Get(ctx, bld, bs), ShouldBeNil) 153 So(bld.Status, ShouldEqual, pb.Status_INFRA_FAILURE) 154 So(bs.Status, ShouldEqual, pb.Status_INFRA_FAILURE) 155 So(sch.Tasks(), ShouldHaveLength, 4) 156 }) 157 158 Convey("not exceed scheduling timeout", func() { 159 now := baseTime.Add(59 * time.Second) 160 ctx, _ = testclock.UseTime(ctx, now) 161 162 err := CheckLiveness(ctx, 1, 10) 163 So(err, ShouldBeNil) 164 // continuation task 165 So(sch.Tasks(), ShouldHaveLength, 1) 166 So(sch.Tasks().Payloads()[0], ShouldResembleProto, &taskdefs.CheckBuildLiveness{ 167 BuildId: 1, 168 HeartbeatTimeout: 10, 169 }) 170 }) 171 172 Convey("not exceed heartbeat timeout", func() { 173 bld.Proto.Status = pb.Status_STARTED 174 bld.Proto.StartTime = timestamppb.New(baseTime) 175 bs.Status = pb.Status_STARTED 176 So(datastore.Put(ctx, bld), ShouldBeNil) 177 now := baseTime.Add(9 * time.Second) 178 ctx, _ = testclock.UseTime(ctx, now) 179 180 err := CheckLiveness(ctx, 1, 10) 181 So(err, ShouldBeNil) 182 // continuation task 183 So(sch.Tasks(), ShouldHaveLength, 1) 184 So(sch.Tasks().Payloads()[0], ShouldResembleProto, &taskdefs.CheckBuildLiveness{ 185 BuildId: 1, 186 HeartbeatTimeout: 10, 187 }) 188 So(sch.Tasks()[0].ETA, ShouldEqual, now.Add(10*time.Second)) 189 }) 190 191 Convey("not exceed any timout && heartbeat timeout not set", func() { 192 bld.Proto.Status = pb.Status_STARTED 193 bld.Proto.StartTime = timestamppb.New(baseTime) 194 bs.Status = pb.Status_STARTED 195 So(datastore.Put(ctx, bld), ShouldBeNil) 196 now := baseTime.Add(9 * time.Second) 197 ctx, _ = testclock.UseTime(ctx, now) 198 199 err := CheckLiveness(ctx, 1, 0) 200 So(err, ShouldBeNil) 201 So(sch.Tasks(), ShouldHaveLength, 1) 202 So(sch.Tasks().Payloads()[0], ShouldResembleProto, &taskdefs.CheckBuildLiveness{ 203 BuildId: 1, 204 HeartbeatTimeout: 0, 205 }) 206 So(sch.Tasks()[0].ETA, ShouldEqual, baseTime.Add(120*time.Second)) 207 }) 208 }) 209 }