go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/swarming/server/bq/bqconverter_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 bq
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  	"time"
    21  
    22  	"google.golang.org/protobuf/types/known/timestamppb"
    23  
    24  	"go.chromium.org/luci/gae/impl/memory"
    25  	"go.chromium.org/luci/gae/service/datastore"
    26  
    27  	bqpb "go.chromium.org/luci/swarming/proto/api"
    28  	"go.chromium.org/luci/swarming/proto/api_v2"
    29  	configpb "go.chromium.org/luci/swarming/proto/config"
    30  	"go.chromium.org/luci/swarming/server/model"
    31  
    32  	. "github.com/smartystreets/goconvey/convey"
    33  	. "go.chromium.org/luci/common/testing/assertions"
    34  )
    35  
    36  func createSampleTaskRequest(key *datastore.Key, testTime time.Time) model.TaskRequest {
    37  	taskSlice := func(val string, exp time.Time) model.TaskSlice {
    38  		return model.TaskSlice{
    39  			Properties: model.TaskProperties{
    40  				Idempotent: true,
    41  				Dimensions: model.TaskDimensions{
    42  					"d1": {"v1", "v2"},
    43  					"d2": {val},
    44  				},
    45  				ExecutionTimeoutSecs: 123,
    46  				GracePeriodSecs:      456,
    47  				IOTimeoutSecs:        789,
    48  				Command:              []string{"run", val},
    49  				RelativeCwd:          "./rel/cwd",
    50  				Env: model.Env{
    51  					"k1": "v1",
    52  					"k2": val,
    53  				},
    54  				EnvPrefixes: model.EnvPrefixes{
    55  					"p1": {"v1", "v2"},
    56  					"p2": {val},
    57  				},
    58  				Caches: []model.CacheEntry{
    59  					{Name: "n1", Path: "p1"},
    60  					{Name: "n2", Path: "p2"},
    61  				},
    62  				CASInputRoot: model.CASReference{
    63  					CASInstance: "cas-inst",
    64  					Digest: model.CASDigest{
    65  						Hash:      "cas-hash",
    66  						SizeBytes: 1234,
    67  					},
    68  				},
    69  				CIPDInput: model.CIPDInput{
    70  					Server: "server",
    71  					ClientPackage: model.CIPDPackage{
    72  						PackageName: "client-package",
    73  						Version:     "client-version",
    74  					},
    75  					Packages: []model.CIPDPackage{
    76  						{
    77  							PackageName: "pkg1",
    78  							Version:     "ver1",
    79  							Path:        "path1",
    80  						},
    81  						{
    82  							PackageName: "pkg2",
    83  							Version:     "ver2",
    84  							Path:        "path2",
    85  						},
    86  					},
    87  				},
    88  				Outputs:        []string{"o1", "o2"},
    89  				HasSecretBytes: true,
    90  				Containment: model.Containment{
    91  					LowerPriority:             true,
    92  					ContainmentType:           apipb.ContainmentType_NOT_SPECIFIED,
    93  					LimitProcesses:            456,
    94  					LimitTotalCommittedMemory: 789,
    95  				},
    96  			},
    97  			ExpirationSecs:  int64(exp.Sub(testTime).Seconds()),
    98  			WaitForCapacity: true,
    99  		}
   100  	}
   101  	return model.TaskRequest{
   102  		Key:     key,
   103  		TxnUUID: "txn-uuid",
   104  		TaskSlices: []model.TaskSlice{
   105  			taskSlice("a", testTime.Add(10*time.Minute)),
   106  			taskSlice("b", testTime.Add(20*time.Minute)),
   107  		},
   108  		Created:              testTime,
   109  		Expiration:           testTime.Add(20 * time.Minute),
   110  		Name:                 "name",
   111  		ParentTaskID:         datastore.Nullable[string, datastore.Indexed]{},
   112  		Authenticated:        "user:authenticated",
   113  		User:                 "user",
   114  		Tags:                 []string{"tag1", "tag2"},
   115  		ManualTags:           []string{"tag1"},
   116  		ServiceAccount:       "service-account",
   117  		Realm:                "realm",
   118  		RealmsEnabled:        true,
   119  		SchedulingAlgorithm:  configpb.Pool_SCHEDULING_ALGORITHM_FIFO,
   120  		Priority:             123,
   121  		BotPingToleranceSecs: 456,
   122  		RBEInstance:          "rbe-instance",
   123  		PubSubTopic:          "pubsub-topic",
   124  		PubSubAuthToken:      "pubsub-auth-token",
   125  		PubSubUserData:       "pubsub-user-data",
   126  		ResultDBUpdateToken:  "resultdb-update-token",
   127  		ResultDB:             model.ResultDBConfig{Enable: true},
   128  		HasBuildTask:         true,
   129  	}
   130  }
   131  
   132  func createSampleBQTaskRequest(taskID string, testTime time.Time) *bqpb.TaskRequest {
   133  	taskSlice := func(val string, exp time.Time) *bqpb.TaskSlice {
   134  		return &bqpb.TaskSlice{
   135  			Properties: &bqpb.TaskProperties{
   136  				Idempotent: true,
   137  				Dimensions: []*bqpb.StringListPair{
   138  					{
   139  						Key:    "d1",
   140  						Values: []string{"v1", "v2"},
   141  					},
   142  					{
   143  						Key:    "d2",
   144  						Values: []string{val},
   145  					},
   146  				},
   147  				ExecutionTimeout: seconds(123),
   148  				GracePeriod:      seconds(456),
   149  				IoTimeout:        seconds(789),
   150  				Command:          []string{"run", val},
   151  				RelativeCwd:      "./rel/cwd",
   152  				Env: []*bqpb.StringPair{
   153  					{
   154  						Key:   "k1",
   155  						Value: "v1",
   156  					},
   157  					{
   158  						Key:   "k2",
   159  						Value: val,
   160  					},
   161  				},
   162  				EnvPaths: []*bqpb.StringListPair{
   163  					{
   164  						Key:    "p1",
   165  						Values: []string{"v1", "v2"},
   166  					},
   167  					{
   168  						Key:    "p2",
   169  						Values: []string{val},
   170  					},
   171  				},
   172  				NamedCaches: []*bqpb.NamedCacheEntry{
   173  					{Name: "n1", DestPath: "p1"},
   174  					{Name: "n2", DestPath: "p2"},
   175  				},
   176  				CasInputRoot: &bqpb.CASReference{
   177  					CasInstance: "cas-inst",
   178  					Digest: &bqpb.Digest{
   179  						Hash:      "cas-hash",
   180  						SizeBytes: 1234,
   181  					},
   182  				},
   183  				CipdInputs: []*bqpb.CIPDPackage{
   184  					{
   185  						PackageName: "pkg1",
   186  						Version:     "ver1",
   187  						DestPath:    "path1",
   188  					},
   189  					{
   190  						PackageName: "pkg2",
   191  						Version:     "ver2",
   192  						DestPath:    "path2",
   193  					},
   194  				},
   195  				Outputs:        []string{"o1", "o2"},
   196  				HasSecretBytes: true,
   197  				Containment: &bqpb.Containment{
   198  					ContainmentType: bqpb.Containment_NOT_SPECIFIED,
   199  				},
   200  			},
   201  			Expiration:      seconds(int64(exp.Sub(testTime).Seconds())),
   202  			WaitForCapacity: true,
   203  		}
   204  	}
   205  	return &bqpb.TaskRequest{
   206  		TaskId: taskID,
   207  		TaskSlices: []*bqpb.TaskSlice{
   208  			taskSlice("a", testTime.Add(10*time.Minute)),
   209  			taskSlice("b", testTime.Add(20*time.Minute)),
   210  		},
   211  		Name:             "name",
   212  		CreateTime:       timestamppb.New(testTime),
   213  		ParentTaskId:     "",
   214  		Authenticated:    "user:authenticated",
   215  		User:             "user",
   216  		Tags:             []string{"tag1", "tag2"},
   217  		ServiceAccount:   "service-account",
   218  		Realm:            "realm",
   219  		Priority:         123,
   220  		BotPingTolerance: seconds(456),
   221  		PubsubNotification: &bqpb.PubSub{
   222  			Topic:    "pubsub-topic",
   223  			Userdata: "pubsub-user-data",
   224  		},
   225  		Resultdb: &bqpb.ResultDBCfg{Enable: true},
   226  	}
   227  }
   228  
   229  func TestTaskRequestConversion(t *testing.T) {
   230  	t.Parallel()
   231  	var testTime = time.Date(2023, time.January, 1, 2, 3, 4, 0, time.UTC)
   232  	Convey("Convert TaskRequest with empty parent task", t, func() {
   233  		ctx := memory.Use(context.Background())
   234  		taskID := "65aba3a3e6b99310"
   235  		key, err := model.TaskIDToRequestKey(ctx, taskID)
   236  		So(err, ShouldBeNil)
   237  		sampleRequest := createSampleTaskRequest(key, testTime)
   238  		expected := createSampleBQTaskRequest(taskID, testTime)
   239  		actual := taskRequestToBQ(&sampleRequest)
   240  		So(actual, ShouldResembleProto, expected)
   241  	})
   242  
   243  	Convey("Converting empty EnvPrefixes works", t, func() {
   244  		ctx := memory.Use(context.Background())
   245  		taskID := "65aba3a3e6b99310"
   246  		key, err := model.TaskIDToRequestKey(ctx, taskID)
   247  		So(err, ShouldBeNil)
   248  		sampleRequest := createSampleTaskRequest(key, testTime)
   249  		// Set this field to zero
   250  		sampleRequest.TaskSlices[0].Properties.EnvPrefixes = make(model.EnvPrefixes)
   251  
   252  		// Set this list to zero too
   253  		expected := createSampleBQTaskRequest(taskID, testTime)
   254  		expected.TaskSlices[0].Properties.EnvPaths = make([]*bqpb.StringListPair, 0)
   255  		actual := taskRequestToBQ(&sampleRequest)
   256  		So(err, ShouldBeNil)
   257  		So(actual, ShouldResembleProto, expected)
   258  	})
   259  }