go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/led/job/jobexport/swarming.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 jobexport
    16  
    17  import (
    18  	"go.chromium.org/luci/common/errors"
    19  	"go.chromium.org/luci/led/job"
    20  	swarmingpb "go.chromium.org/luci/swarming/proto/api_v2"
    21  )
    22  
    23  var (
    24  	// In order to have swarming service to upload output to RBE-CAS when no inputs.
    25  	dummyCasDigest = &swarmingpb.Digest{
    26  		Hash:      "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    27  		SizeBytes: 0,
    28  	}
    29  )
    30  
    31  // ToSwarmingNewTask renders a swarming proto task to a
    32  // NewTaskRequest.
    33  func ToSwarmingNewTask(sw *job.Swarming) (*swarmingpb.NewTaskRequest, error) {
    34  	task := sw.Task
    35  	ret := &swarmingpb.NewTaskRequest{
    36  		BotPingToleranceSecs: task.GetBotPingToleranceSecs(),
    37  		Name:                 task.Name,
    38  		User:                 task.User,
    39  		ParentTaskId:         task.ParentTaskId,
    40  		Priority:             task.Priority,
    41  		ServiceAccount:       task.ServiceAccount,
    42  		Realm:                task.Realm,
    43  		Tags:                 task.Tags,
    44  		TaskSlices:           make([]*swarmingpb.TaskSlice, 0, len(task.TaskSlices)),
    45  	}
    46  	if rdbEnabled := task.GetResultdb().GetEnable(); rdbEnabled {
    47  		ret.Resultdb = &swarmingpb.ResultDBCfg{
    48  			Enable: true,
    49  		}
    50  	}
    51  
    52  	casUserPayload := sw.GetCasUserPayload()
    53  	cupDigest := casUserPayload.GetDigest()
    54  	for i, slice := range task.TaskSlices {
    55  		props := slice.Properties
    56  
    57  		slcCasDgst := props.GetCasInputRoot().GetDigest()
    58  		// validate all isolate and rbe-cas related fields.
    59  		if slcCasDgst != nil && cupDigest != nil &&
    60  			(slcCasDgst.Hash != cupDigest.Hash || slcCasDgst.SizeBytes != cupDigest.SizeBytes) {
    61  			return nil, errors.Reason(
    62  				"slice %d defines CasInputRoot, but job.CasUserPayload is also defined. "+
    63  					"Call ConsolidateIsolateds before calling ToSwarmingNewTask.", i).Err()
    64  		}
    65  
    66  		toAdd := &swarmingpb.TaskSlice{
    67  			ExpirationSecs:  slice.ExpirationSecs,
    68  			WaitForCapacity: slice.WaitForCapacity,
    69  			Properties: &swarmingpb.TaskProperties{
    70  				Caches: make([]*swarmingpb.CacheEntry, 0, len(props.Caches)),
    71  
    72  				Dimensions: make([]*swarmingpb.StringPair, 0, len(props.Dimensions)),
    73  
    74  				ExecutionTimeoutSecs: props.GetExecutionTimeoutSecs(),
    75  				GracePeriodSecs:      props.GetGracePeriodSecs(),
    76  				IoTimeoutSecs:        props.GetIoTimeoutSecs(),
    77  
    78  				CipdInput: &swarmingpb.CipdInput{
    79  					Packages: make([]*swarmingpb.CipdPackage, 0, len(props.CipdInput.Packages)),
    80  				},
    81  
    82  				Env:         make([]*swarmingpb.StringPair, 0, len(props.Env)),
    83  				EnvPrefixes: make([]*swarmingpb.StringListPair, 0, len(props.EnvPrefixes)),
    84  
    85  				Command:     props.Command,
    86  				RelativeCwd: props.RelativeCwd,
    87  			},
    88  		}
    89  
    90  		if con := props.GetContainment(); con.GetContainmentType() != swarmingpb.ContainmentType_NOT_SPECIFIED {
    91  			toAdd.Properties.Containment = &swarmingpb.Containment{
    92  				ContainmentType: con.GetContainmentType(),
    93  			}
    94  		}
    95  
    96  		// If we have rbe-cas digest info, use that info.
    97  		// Otherwise,  populate a dummy rbe-cas prop.
    98  		//
    99  		// The digest info in the slice will be used first. If it's not there, then
   100  		// fall back to use the info in job-global "CasUserPayload"
   101  		//
   102  		// (The twisted logic will look a little bit better, after completely getting rid of isolate.)
   103  
   104  		var casToUse *swarmingpb.CASReference
   105  		sliceCas := props.CasInputRoot
   106  		jobCas := casUserPayload
   107  		switch {
   108  		case sliceCas.GetDigest().GetHash() != "":
   109  			casToUse = sliceCas
   110  		case jobCas.GetDigest().GetHash() != "":
   111  			casToUse = jobCas
   112  		case sliceCas.GetCasInstance() != "":
   113  			casToUse = sliceCas
   114  		default:
   115  			casToUse = jobCas
   116  		}
   117  
   118  		if casToUse != nil {
   119  			toAdd.Properties.CasInputRoot = &swarmingpb.CASReference{
   120  				CasInstance: casToUse.CasInstance,
   121  				Digest: &swarmingpb.Digest{
   122  					Hash:      casToUse.Digest.GetHash(),
   123  					SizeBytes: casToUse.Digest.GetSizeBytes(),
   124  				},
   125  			}
   126  		} else {
   127  			// populate a dummy CasInputRoot in order to use RBE-CAS.
   128  			casIns, err := job.ToCasInstance(sw.Hostname)
   129  			if err != nil {
   130  				return nil, err
   131  			}
   132  			toAdd.Properties.CasInputRoot = &swarmingpb.CASReference{
   133  				CasInstance: casIns,
   134  				Digest:      dummyCasDigest,
   135  			}
   136  		}
   137  		if toAdd.Properties.CasInputRoot.Digest.Hash == "" {
   138  			toAdd.Properties.CasInputRoot.Digest = dummyCasDigest
   139  		}
   140  
   141  		for _, env := range props.Env {
   142  			toAdd.Properties.Env = append(toAdd.Properties.Env, &swarmingpb.StringPair{
   143  				Key:   env.Key,
   144  				Value: env.Value,
   145  			})
   146  		}
   147  
   148  		for _, path := range props.EnvPrefixes {
   149  			toAdd.Properties.EnvPrefixes = append(toAdd.Properties.EnvPrefixes, &swarmingpb.StringListPair{
   150  				Key:   path.Key,
   151  				Value: path.Value,
   152  			})
   153  		}
   154  
   155  		for _, cache := range props.Caches {
   156  			toAdd.Properties.Caches = append(toAdd.Properties.Caches, &swarmingpb.CacheEntry{
   157  				Name: cache.Name,
   158  				Path: cache.Path,
   159  			})
   160  		}
   161  
   162  		for _, pkg := range props.CipdInput.Packages {
   163  			toAdd.Properties.CipdInput.Packages = append(toAdd.Properties.CipdInput.Packages, &swarmingpb.CipdPackage{
   164  				PackageName: pkg.PackageName,
   165  				Version:     pkg.Version,
   166  				Path:        pkg.Path,
   167  			})
   168  		}
   169  
   170  		for _, dim := range props.Dimensions {
   171  			toAdd.Properties.Dimensions = append(toAdd.Properties.Dimensions, &swarmingpb.StringPair{
   172  				Key:   dim.Key,
   173  				Value: dim.Value,
   174  			})
   175  		}
   176  
   177  		ret.TaskSlices = append(ret.TaskSlices, toAdd)
   178  	}
   179  
   180  	return ret, nil
   181  }