blob: 5ba50b788c45077ae2e756a4dd72f44ff45c3694 [file] [log] [blame]
// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package job
import (
"context"
"os"
"testing"
"google.golang.org/protobuf/encoding/protojson"
"go.chromium.org/luci/buildbucket/cmd/bbagent/bbinput"
bbpb "go.chromium.org/luci/buildbucket/proto"
"go.chromium.org/luci/common/data/rand/cryptorand"
"go.chromium.org/luci/led/job/experiments"
"go.chromium.org/luci/luciexe/exe"
swarmingpb "go.chromium.org/luci/swarming/proto/api_v2"
. "github.com/smartystreets/goconvey/convey"
. "go.chromium.org/luci/common/testing/assertions"
)
const phonyTagExperiment = "luci.test.add_phony_tag_experiment"
func init() {
experiments.Register(phonyTagExperiment, func(ctx context.Context, b *bbpb.Build, task *swarmingpb.NewTaskRequest) error {
task.Tags = append(task.Tags, "phony_tag:experiment")
return nil
})
}
func TestFlattenToSwarming(t *testing.T) {
t.Parallel()
Convey(`FlattenToSwarming`, t, func() {
fixture, err := os.ReadFile("jobcreate/testdata/bbagent_cas.job.json")
So(err, ShouldBeNil)
ctx := cryptorand.MockForTest(context.Background(), 4)
bbJob := &Definition{}
So(protojson.Unmarshal(fixture, bbJob), ShouldBeNil)
bb := bbJob.GetBuildbucket()
totalExpiration := bb.BbagentArgs.Build.SchedulingTimeout.Seconds
Convey(`bbagent`, func() {
So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil)
sw := bbJob.GetSwarming()
So(sw, ShouldNotBeNil)
So(sw.Task.Tags, ShouldResemble, []string{
"allow_milo:1",
"log_location:logdog://luci-logdog-dev.appspot.com/infra/led/username/1dd4751f899d743d0780c9644375aae211327818655f3d20f84abef6a9df0898/+/build.proto",
})
So(sw.Task.TaskSlices, ShouldHaveLength, 2)
slice0 := sw.Task.TaskSlices[0]
So(slice0.ExpirationSecs, ShouldEqual, 240)
So(slice0.Properties.Dimensions, ShouldResembleProto, []*swarmingpb.StringPair{
{
Key: "caches",
Value: "builder_1d1f048016f3dc7294e1abddfd758182bc95619cec2a87d01a3f24517b4e2814_v2",
},
{Key: "cpu", Value: "x86-64"},
{Key: "os", Value: "Ubuntu"},
{Key: "pool", Value: "Chrome"},
})
So(slice0.Properties.Command[:3], ShouldResemble, []string{
"bbagent${EXECUTABLE_SUFFIX}", "--output",
"${ISOLATED_OUTDIR}/build.proto.json",
})
bbArgs, err := bbinput.Parse(slice0.Properties.Command[3])
So(err, ShouldBeNil)
So(bbArgs.PayloadPath, ShouldResemble, "kitchen-checkout")
So(bbArgs.Build.Exe.Cmd, ShouldResemble, []string{"luciexe"})
props := ledProperties{}
err = exe.ParseProperties(bbArgs.Build.Input.Properties, map[string]any{
"$recipe_engine/led": &props,
})
So(err, ShouldBeNil)
expectedProps := ledProperties{
LedRunID: "infra/led/username/1dd4751f899d743d0780c9644375aae211327818655f3d20f84abef6a9df0898",
CIPDInput: &cipdInput{
"infra/recipe_bundles/chromium.googlesource.com/infra/luci/recipes-py",
"HEAD",
},
}
So(props, ShouldResemble, expectedProps)
// Added `kitchen-checkout` as the last CipdInputs entry.
So(slice0.Properties.CipdInput.Packages, ShouldHaveLength, 17) // see bbagent.job.json
So(slice0.Properties.CipdInput.Packages[16], ShouldResembleProto, &swarmingpb.CipdPackage{
Path: "kitchen-checkout",
PackageName: expectedProps.CIPDInput.Package,
Version: expectedProps.CIPDInput.Version,
})
slice1 := sw.Task.TaskSlices[1]
So(slice1.ExpirationSecs, ShouldEqual, 21360)
So(slice1.Properties.Dimensions, ShouldResembleProto, []*swarmingpb.StringPair{
{Key: "cpu", Value: "x86-64"},
{Key: "os", Value: "Ubuntu"},
{Key: "pool", Value: "Chrome"},
})
So(slice0.ExpirationSecs+slice1.ExpirationSecs, ShouldEqual, totalExpiration)
})
Convey(`kitchen`, func() {
bb.LegacyKitchen = true
So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldErrLike,
"kitchen job Definitions not supported")
})
Convey(`explicit max expiration`, func() {
// set a dimension to expire after end of current task
editDims(bbJob, "final=value@40000")
So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil)
sw := bbJob.GetSwarming()
So(sw, ShouldNotBeNil)
So(sw.Task.TaskSlices, ShouldHaveLength, 2)
So(sw.Task.TaskSlices[0].ExpirationSecs, ShouldEqual, 240)
So(sw.Task.TaskSlices[1].ExpirationSecs, ShouldEqual, 40000-240)
})
Convey(`no expiring dims`, func() {
bb := bbJob.GetBuildbucket()
for _, dim := range bb.GetBbagentArgs().Build.Infra.Swarming.TaskDimensions {
dim.Expiration = nil
}
for _, cache := range bb.GetBbagentArgs().Build.Infra.Swarming.Caches {
cache.WaitForWarmCache = nil
}
So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil)
sw := bbJob.GetSwarming()
So(sw, ShouldNotBeNil)
So(sw.Task.TaskSlices, ShouldHaveLength, 1)
So(sw.Task.TaskSlices[0].ExpirationSecs, ShouldEqual, 21600)
})
Convey(`CasUserPayload recipe`, func() {
SoHLEdit(bbJob, func(je HighLevelEditor) {
je.TaskPayloadSource("", "")
je.CASTaskPayload("some/path", &swarmingpb.CASReference{
CasInstance: "projects/chromium-swarm-dev/instances/default_instance",
Digest: &swarmingpb.Digest{
Hash: "b7c329e532e221e23809ba23f9af5b309aa17d490d845580207493d381998bd9",
SizeBytes: 24,
},
})
})
So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil)
sw := bbJob.GetSwarming()
So(sw, ShouldNotBeNil)
So(sw.Task.TaskSlices, ShouldHaveLength, 2)
slice0 := sw.Task.TaskSlices[0]
for _, pkg := range slice0.Properties.CipdInput.Packages {
// there shouldn't be any recipe package any more
So(pkg.Version, ShouldNotEqual, "HEAD")
}
So(slice0.Properties.Command[:3], ShouldResemble, []string{
"bbagent${EXECUTABLE_SUFFIX}", "--output",
"${ISOLATED_OUTDIR}/build.proto.json",
})
bbArgs, err := bbinput.Parse(slice0.Properties.Command[3])
So(err, ShouldBeNil)
So(bbArgs.PayloadPath, ShouldResemble, "some/path")
So(bbArgs.Build.Exe.Cmd, ShouldResemble, []string{"luciexe"})
props := ledProperties{}
err = exe.ParseProperties(bbArgs.Build.Input.Properties, map[string]any{
"$recipe_engine/led": &props,
})
So(err, ShouldBeNil)
expectedProps := ledProperties{
LedRunID: "infra/led/username/1dd4751f899d743d0780c9644375aae211327818655f3d20f84abef6a9df0898",
RbeCasInput: &swarmingpb.CASReference{
CasInstance: "projects/chromium-swarm-dev/instances/default_instance",
Digest: &swarmingpb.Digest{
Hash: "b7c329e532e221e23809ba23f9af5b309aa17d490d845580207493d381998bd9",
SizeBytes: 24,
},
},
}
So(props, ShouldResemble, expectedProps)
})
Convey(`With experiments`, func() {
SoHLEdit(bbJob, func(je HighLevelEditor) {
je.Experiments(map[string]bool{
phonyTagExperiment: true, // see init() above
"should_be_skipped_as_unknown": true,
})
})
So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil)
sw := bbJob.GetSwarming()
So(sw, ShouldNotBeNil)
So(sw.Task.Tags[len(sw.Task.Tags)-1], ShouldEqual, "phony_tag:experiment")
})
})
}