go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/led/job/job_test.go (about) 1 // Copyright 2020 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 job 16 17 import ( 18 "context" 19 "os" 20 "testing" 21 22 "google.golang.org/protobuf/encoding/protojson" 23 24 "go.chromium.org/luci/buildbucket/cmd/bbagent/bbinput" 25 bbpb "go.chromium.org/luci/buildbucket/proto" 26 "go.chromium.org/luci/common/data/rand/cryptorand" 27 "go.chromium.org/luci/led/job/experiments" 28 "go.chromium.org/luci/luciexe/exe" 29 swarmingpb "go.chromium.org/luci/swarming/proto/api_v2" 30 31 . "github.com/smartystreets/goconvey/convey" 32 . "go.chromium.org/luci/common/testing/assertions" 33 ) 34 35 const phonyTagExperiment = "luci.test.add_phony_tag_experiment" 36 37 func init() { 38 experiments.Register(phonyTagExperiment, func(ctx context.Context, b *bbpb.Build, task *swarmingpb.NewTaskRequest) error { 39 task.Tags = append(task.Tags, "phony_tag:experiment") 40 return nil 41 }) 42 } 43 44 func TestFlattenToSwarming(t *testing.T) { 45 t.Parallel() 46 47 Convey(`FlattenToSwarming`, t, func() { 48 fixture, err := os.ReadFile("jobcreate/testdata/bbagent_cas.job.json") 49 So(err, ShouldBeNil) 50 51 ctx := cryptorand.MockForTest(context.Background(), 4) 52 53 bbJob := &Definition{} 54 So(protojson.Unmarshal(fixture, bbJob), ShouldBeNil) 55 bb := bbJob.GetBuildbucket() 56 totalExpiration := bb.BbagentArgs.Build.SchedulingTimeout.Seconds 57 58 Convey(`bbagent`, func() { 59 So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil) 60 61 sw := bbJob.GetSwarming() 62 So(sw, ShouldNotBeNil) 63 So(sw.Task.Tags, ShouldResemble, []string{ 64 "allow_milo:1", 65 "log_location:logdog://luci-logdog-dev.appspot.com/infra/led/username/1dd4751f899d743d0780c9644375aae211327818655f3d20f84abef6a9df0898/+/build.proto", 66 }) 67 68 So(sw.Task.TaskSlices, ShouldHaveLength, 2) 69 70 slice0 := sw.Task.TaskSlices[0] 71 So(slice0.ExpirationSecs, ShouldEqual, 240) 72 So(slice0.Properties.Dimensions, ShouldResembleProto, []*swarmingpb.StringPair{ 73 { 74 Key: "caches", 75 Value: "builder_1d1f048016f3dc7294e1abddfd758182bc95619cec2a87d01a3f24517b4e2814_v2", 76 }, 77 {Key: "cpu", Value: "x86-64"}, 78 {Key: "os", Value: "Ubuntu"}, 79 {Key: "pool", Value: "Chrome"}, 80 }) 81 82 So(slice0.Properties.Command[:3], ShouldResemble, []string{ 83 "bbagent${EXECUTABLE_SUFFIX}", "--output", 84 "${ISOLATED_OUTDIR}/build.proto.json", 85 }) 86 bbArgs, err := bbinput.Parse(slice0.Properties.Command[3]) 87 So(err, ShouldBeNil) 88 89 So(bbArgs.PayloadPath, ShouldResemble, "kitchen-checkout") 90 So(bbArgs.Build.Exe.Cmd, ShouldResemble, []string{"luciexe"}) 91 92 props := ledProperties{} 93 err = exe.ParseProperties(bbArgs.Build.Input.Properties, map[string]any{ 94 "$recipe_engine/led": &props, 95 }) 96 So(err, ShouldBeNil) 97 98 expectedProps := ledProperties{ 99 LedRunID: "infra/led/username/1dd4751f899d743d0780c9644375aae211327818655f3d20f84abef6a9df0898", 100 CIPDInput: &cipdInput{ 101 "infra/recipe_bundles/chromium.googlesource.com/infra/luci/recipes-py", 102 "HEAD", 103 }, 104 } 105 So(props, ShouldResemble, expectedProps) 106 107 // Added `kitchen-checkout` as the last CipdInputs entry. 108 So(slice0.Properties.CipdInput.Packages, ShouldHaveLength, 17) // see bbagent.job.json 109 So(slice0.Properties.CipdInput.Packages[16], ShouldResembleProto, &swarmingpb.CipdPackage{ 110 Path: "kitchen-checkout", 111 PackageName: expectedProps.CIPDInput.Package, 112 Version: expectedProps.CIPDInput.Version, 113 }) 114 115 slice1 := sw.Task.TaskSlices[1] 116 So(slice1.ExpirationSecs, ShouldEqual, 21360) 117 So(slice1.Properties.Dimensions, ShouldResembleProto, []*swarmingpb.StringPair{ 118 {Key: "cpu", Value: "x86-64"}, 119 {Key: "os", Value: "Ubuntu"}, 120 {Key: "pool", Value: "Chrome"}, 121 }) 122 123 So(slice0.ExpirationSecs+slice1.ExpirationSecs, ShouldEqual, totalExpiration) 124 }) 125 126 Convey(`kitchen`, func() { 127 bb.LegacyKitchen = true 128 So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldErrLike, 129 "kitchen job Definitions not supported") 130 }) 131 132 Convey(`explicit max expiration`, func() { 133 // set a dimension to expire after end of current task 134 editDims(bbJob, "final=value@40000") 135 136 So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil) 137 138 sw := bbJob.GetSwarming() 139 So(sw, ShouldNotBeNil) 140 So(sw.Task.TaskSlices, ShouldHaveLength, 2) 141 142 So(sw.Task.TaskSlices[0].ExpirationSecs, ShouldEqual, 240) 143 So(sw.Task.TaskSlices[1].ExpirationSecs, ShouldEqual, 40000-240) 144 }) 145 146 Convey(`no expiring dims`, func() { 147 bb := bbJob.GetBuildbucket() 148 for _, dim := range bb.GetBbagentArgs().Build.Infra.Swarming.TaskDimensions { 149 dim.Expiration = nil 150 } 151 for _, cache := range bb.GetBbagentArgs().Build.Infra.Swarming.Caches { 152 cache.WaitForWarmCache = nil 153 } 154 155 So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil) 156 157 sw := bbJob.GetSwarming() 158 So(sw, ShouldNotBeNil) 159 So(sw.Task.TaskSlices, ShouldHaveLength, 1) 160 161 So(sw.Task.TaskSlices[0].ExpirationSecs, ShouldEqual, 21600) 162 }) 163 164 Convey(`CasUserPayload recipe`, func() { 165 SoHLEdit(bbJob, func(je HighLevelEditor) { 166 je.TaskPayloadSource("", "") 167 je.CASTaskPayload("some/path", &swarmingpb.CASReference{ 168 CasInstance: "projects/chromium-swarm-dev/instances/default_instance", 169 Digest: &swarmingpb.Digest{ 170 Hash: "b7c329e532e221e23809ba23f9af5b309aa17d490d845580207493d381998bd9", 171 SizeBytes: 24, 172 }, 173 }) 174 }) 175 176 So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil) 177 178 sw := bbJob.GetSwarming() 179 So(sw, ShouldNotBeNil) 180 So(sw.Task.TaskSlices, ShouldHaveLength, 2) 181 182 slice0 := sw.Task.TaskSlices[0] 183 for _, pkg := range slice0.Properties.CipdInput.Packages { 184 // there shouldn't be any recipe package any more 185 So(pkg.Version, ShouldNotEqual, "HEAD") 186 } 187 188 So(slice0.Properties.Command[:3], ShouldResemble, []string{ 189 "bbagent${EXECUTABLE_SUFFIX}", "--output", 190 "${ISOLATED_OUTDIR}/build.proto.json", 191 }) 192 bbArgs, err := bbinput.Parse(slice0.Properties.Command[3]) 193 So(err, ShouldBeNil) 194 195 So(bbArgs.PayloadPath, ShouldResemble, "some/path") 196 So(bbArgs.Build.Exe.Cmd, ShouldResemble, []string{"luciexe"}) 197 198 props := ledProperties{} 199 err = exe.ParseProperties(bbArgs.Build.Input.Properties, map[string]any{ 200 "$recipe_engine/led": &props, 201 }) 202 So(err, ShouldBeNil) 203 expectedProps := ledProperties{ 204 LedRunID: "infra/led/username/1dd4751f899d743d0780c9644375aae211327818655f3d20f84abef6a9df0898", 205 RbeCasInput: &swarmingpb.CASReference{ 206 CasInstance: "projects/chromium-swarm-dev/instances/default_instance", 207 Digest: &swarmingpb.Digest{ 208 Hash: "b7c329e532e221e23809ba23f9af5b309aa17d490d845580207493d381998bd9", 209 SizeBytes: 24, 210 }, 211 }, 212 } 213 So(props, ShouldResemble, expectedProps) 214 }) 215 Convey(`With experiments`, func() { 216 SoHLEdit(bbJob, func(je HighLevelEditor) { 217 je.Experiments(map[string]bool{ 218 phonyTagExperiment: true, // see init() above 219 "should_be_skipped_as_unknown": true, 220 }) 221 }) 222 223 So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil) 224 225 sw := bbJob.GetSwarming() 226 So(sw, ShouldNotBeNil) 227 So(sw.Task.Tags[len(sw.Task.Tags)-1], ShouldEqual, "phony_tag:experiment") 228 }) 229 }) 230 231 }