blob: 5f2037c6f22bf236622f10e7e335a1e06199792f [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package request provides a library to create swarming requests based on
// skylab test or task parameters.
package request
import (
"fmt"
"strings"
swarming "go.chromium.org/luci/common/api/swarming/swarming/v1"
"go.chromium.org/luci/common/data/strpair"
"go.chromium.org/luci/common/errors"
"infra/libs/skylab/worker"
)
// Args defines the set of arguments for creating a request.
type Args struct {
Cmd worker.Command
Tags []string
ProvisionableDimensions []string
Dimensions []string
TimeoutMins int
Priority int64
ParentTaskID string
}
// New creates a new swarming request for the given worker command and parameters.
func New(args Args) (*swarming.SwarmingRpcsNewTaskRequest, error) {
slices, err := getSlices(args.Cmd, args.ProvisionableDimensions, args.Dimensions, args.TimeoutMins)
if err != nil {
return nil, errors.Annotate(err, "create request").Err()
}
req := &swarming.SwarmingRpcsNewTaskRequest{
Name: args.Cmd.TaskName,
Tags: args.Tags,
TaskSlices: slices,
Priority: args.Priority,
ParentTaskId: args.ParentTaskID,
}
return req, nil
}
// getSlices generates and returns the set of swarming task slices for the given test task.
func getSlices(cmd worker.Command, provisionableDimensions []string, dimensions []string, timeoutMins int) ([]*swarming.SwarmingRpcsTaskSlice, error) {
slices := make([]*swarming.SwarmingRpcsTaskSlice, 1, 2)
basePairs, err := toPairs(dimensions)
if err != nil {
return nil, errors.Annotate(err, "create slices").Err()
}
provisionablePairs, err := toPairs(provisionableDimensions)
if err != nil {
return nil, errors.Annotate(err, "create slices").Err()
}
s0Dims := append(basePairs, provisionablePairs...)
slices[0] = taskSlice(cmd.Args(), s0Dims, timeoutMins)
if len(provisionableDimensions) != 0 {
cmd.ProvisionLabels = provisionDimensionsToLabels(provisionableDimensions)
s1Dims := basePairs
slices = append(slices, taskSlice(cmd.Args(), s1Dims, timeoutMins))
}
finalSlice := slices[len(slices)-1]
finalSlice.ExpirationSecs = int64(timeoutMins * 60)
return slices, nil
}
func taskSlice(command []string, dimensions []*swarming.SwarmingRpcsStringPair, timeoutMins int) *swarming.SwarmingRpcsTaskSlice {
return &swarming.SwarmingRpcsTaskSlice{
// We want all slices to wait, at least a little while, for bots with
// metching dimensions.
// For slice 0: This allows the task to try to re-use provisionable
// labels that get set by previous tasks with the same label that are
// about to finish.
// For slice 1: This allows the task to wait for devices to get
// repaired, if there are no devices with dut_state:ready.
WaitForCapacity: true,
// Slice 0 should have a fairly short expiration time, to reduce
// overhead for tasks that are the first ones enqueue with a particular
// provisionable label. This value will be overwritten for the final
// slice of a task.
ExpirationSecs: 30,
Properties: &swarming.SwarmingRpcsTaskProperties{
Command: command,
Dimensions: dimensions,
ExecutionTimeoutSecs: int64(timeoutMins * 60),
},
}
}
// provisionDimensionsToLabels converts provisionable dimensions to labels.
func provisionDimensionsToLabels(dims []string) []string {
labels := make([]string, len(dims))
for i, l := range dims {
labels[i] = strings.TrimPrefix(l, "provisionable-")
}
return labels
}
// toPairs converts a slice of strings in foo:bar form to a slice of swarming
// rpc string pairs.
func toPairs(dimensions []string) ([]*swarming.SwarmingRpcsStringPair, error) {
pairs := make([]*swarming.SwarmingRpcsStringPair, len(dimensions))
for i, d := range dimensions {
k, v := strpair.Parse(d)
if v == "" {
return nil, fmt.Errorf("malformed dimension with key '%s' has no value", k)
}
pairs[i] = &swarming.SwarmingRpcsStringPair{Key: k, Value: v}
}
return pairs, nil
}