go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/swarming/server/cfg/pools.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 cfg
    16  
    17  import (
    18  	"strings"
    19  
    20  	"go.chromium.org/luci/common/data/stringset"
    21  	"go.chromium.org/luci/common/errors"
    22  	"go.chromium.org/luci/config/validation"
    23  	"go.chromium.org/luci/server/auth/realms"
    24  
    25  	configpb "go.chromium.org/luci/swarming/proto/config"
    26  	"go.chromium.org/luci/swarming/server/validate"
    27  )
    28  
    29  // Pool is a parsed config of some single pool.
    30  type Pool struct {
    31  	// Realm is a realm with ACLs for this pool's resources.
    32  	//
    33  	// This is a global realm name, e.g. `infra:pool/flex/try`.
    34  	Realm string
    35  
    36  	// DefaultTaskRealm is a realm for tasks if they don't have a realm set.
    37  	//
    38  	// It is optional. If not set, all tasks must have the realm specified by
    39  	// the caller when they are created.
    40  	DefaultTaskRealm string
    41  
    42  	// TODO(vadimsh): Implement task templates.
    43  }
    44  
    45  // newPoolsConfig converts pools.cfg proto it a queryable map.
    46  //
    47  // pools.cfg here already passed the validation when it was first ingested. It
    48  // is possible the server code itself changed and the existing config is no
    49  // longer correct in some bad way. An error is returned in that case.
    50  //
    51  // On success returns a map "pool name => its config".
    52  func newPoolsConfig(cfg *configpb.PoolsCfg) (map[string]*Pool, error) {
    53  	pools := map[string]*Pool{}
    54  	for _, pb := range cfg.Pool {
    55  		cfg, err := newPool(pb)
    56  		if err != nil {
    57  			return nil, errors.Annotate(err, "broken pools.cfg entry: %s", pb).Err()
    58  		}
    59  		for _, name := range pb.Name {
    60  			pools[name] = cfg
    61  		}
    62  	}
    63  	return pools, nil
    64  }
    65  
    66  // newPool processes a single configpb.Pool definition.
    67  func newPool(pb *configpb.Pool) (*Pool, error) {
    68  	// TODO(vadimsh): Process TaskDeploymentScheme.
    69  	return &Pool{
    70  		Realm:            pb.Realm,
    71  		DefaultTaskRealm: pb.DefaultTaskRealm,
    72  	}, nil
    73  }
    74  
    75  // validatePoolsCfg validates pools.cfg, writing errors into `ctx`.
    76  func validatePoolsCfg(ctx *validation.Context, cfg *configpb.PoolsCfg) {
    77  	pools := stringset.New(0)
    78  
    79  	validatePool := func(pb *configpb.Pool) {
    80  		// Deprecated fields that must not be set.
    81  		if pb.Schedulers != nil {
    82  			ctx.Errorf("setting deprecated field `schedulers`")
    83  		}
    84  		if pb.BotMonitoring != "" {
    85  			ctx.Errorf("setting deprecated field `bot_monitoring`")
    86  		}
    87  
    88  		if len(pb.Name) == 0 {
    89  			ctx.Errorf("at least one pool name must be given")
    90  		}
    91  		for _, name := range pb.Name {
    92  			if err := validate.DimensionValue(name); err != nil {
    93  				ctx.Errorf("bad pool name %q: %s", name, err)
    94  			}
    95  			if !pools.Add(name) {
    96  				ctx.Errorf("pool %q was already declared", name)
    97  			}
    98  		}
    99  
   100  		// Realm is required.
   101  		if pb.Realm == "" {
   102  			ctx.Errorf("missing required `realm` field")
   103  		} else if err := realms.ValidateRealmName(pb.Realm, realms.GlobalScope); err != nil {
   104  			ctx.Errorf("bad `realm` field: %s", err)
   105  		}
   106  
   107  		// DefaultTaskRealm is optional.
   108  		if pb.DefaultTaskRealm != "" {
   109  			if err := realms.ValidateRealmName(pb.DefaultTaskRealm, realms.GlobalScope); err != nil {
   110  				ctx.Errorf("bad `default_task_realm` field: %s", err)
   111  			}
   112  		}
   113  
   114  		// TODO(vadimsh): Validate task template settings.
   115  
   116  		// Silently skip remaining fields that are used by the Python implementation
   117  		// but ignored by the Go implementation:
   118  		//	ExternalSchedulers: external schedulers not supported in Go.
   119  		//	EnforcedRealmPermissions: realm permissions are always enforced.
   120  		//	RbeMigration: RBE is the only supported scheduler.
   121  		//	SchedulingAlgorithm: RBE doesn't support custom scheduling algorithms.
   122  	}
   123  
   124  	for idx, pool := range cfg.Pool {
   125  		title := "unnamed"
   126  		if len(pool.Name) != 0 {
   127  			title = strings.Join(pool.Name, ",")
   128  		}
   129  		ctx.Enter("pool #%d (%s)", idx+1, title)
   130  		validatePool(pool)
   131  		ctx.Exit()
   132  	}
   133  }