golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/coordinator/pool/pool.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build linux || darwin
     6  
     7  package pool
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"log"
    13  	"math/rand"
    14  	"strings"
    15  	"time"
    16  
    17  	"golang.org/x/build/buildlet"
    18  	"golang.org/x/build/dashboard"
    19  	"golang.org/x/build/internal/coordinator/pool/queue"
    20  )
    21  
    22  // Buildlet defines an interface for a pool of buildlets.
    23  type Buildlet interface {
    24  	// GetBuildlet returns a new buildlet client.
    25  	//
    26  	// The hostType is the key into the dashboard.Hosts
    27  	// map (such as "host-linux-bullseye"), NOT the buidler type
    28  	// ("linux-386").
    29  	//
    30  	// Users of GetBuildlet must both call Client.Close when done
    31  	// with the client as well as cancel the provided Context.
    32  	GetBuildlet(ctx context.Context, hostType string, lg Logger, item *queue.SchedItem) (buildlet.Client, error)
    33  
    34  	String() string // TODO(bradfitz): more status stuff
    35  }
    36  
    37  // IsRemoteBuildletFunc should report whether the buildlet instance name is
    38  // is a remote buildlet. This is applicable to GCE and EC2 instances.
    39  //
    40  // TODO(golang.org/issue/38337): should be removed once remote buildlet management
    41  // functions are moved into a package.
    42  type IsRemoteBuildletFunc func(instanceName string) bool
    43  
    44  // randHex generates a random hex string.
    45  func randHex(n int) string {
    46  	buf := make([]byte, n/2+1)
    47  	if _, err := rand.Read(buf); err != nil {
    48  		log.Fatalf("randHex: %v", err)
    49  	}
    50  	return fmt.Sprintf("%x", buf)[:n]
    51  }
    52  
    53  func friendlyDuration(d time.Duration) string {
    54  	if d > 10*time.Second {
    55  		d2 := ((d + 50*time.Millisecond) / (100 * time.Millisecond)) * (100 * time.Millisecond)
    56  		return d2.String()
    57  	}
    58  	if d > time.Second {
    59  		d2 := ((d + 5*time.Millisecond) / (10 * time.Millisecond)) * (10 * time.Millisecond)
    60  		return d2.String()
    61  	}
    62  	d2 := ((d + 50*time.Microsecond) / (100 * time.Microsecond)) * (100 * time.Microsecond)
    63  	return d2.String()
    64  }
    65  
    66  // instanceName generates a random instance name according to the host type.
    67  func instanceName(hostType string, length int) string {
    68  	return fmt.Sprintf("buildlet-%s-rn%s", strings.TrimPrefix(hostType, "host-"), randHex(length))
    69  }
    70  
    71  // determineDeleteTimeout reports the buildlet delete timeout duration
    72  // with the following priority:
    73  //
    74  // 1. Host type override from host config.
    75  // 2. Global default.
    76  func determineDeleteTimeout(host *dashboard.HostConfig) time.Duration {
    77  	if host.CustomDeleteTimeout != 0 {
    78  		return host.CustomDeleteTimeout
    79  	}
    80  
    81  	// The value we return below is effectively a global default.
    82  	//
    83  	// The comment of CleanUpOldVMs (and CleanUpOldPodsLoop) includes:
    84  	//
    85  	//	This is the safety mechanism to delete VMs which stray from the
    86  	//	normal deleting process. VMs are created to run a single build and
    87  	//	should be shut down by a controlling process. Due to various types
    88  	//	of failures, they might get stranded. To prevent them from getting
    89  	//	stranded and wasting resources forever, we instead set the
    90  	//	"delete-at" metadata attribute on them when created to some time
    91  	//	that's well beyond their expected lifetime.
    92  	//
    93  	// Issue go.dev/issue/52929 tracks what to do about this global
    94  	// timeout in the long term. Unless something changes,
    95  	// it needs to be maintained manually so that it's always
    96  	// "well beyond their expected lifetime" of each builder that doesn't
    97  	// otherwise override this timeout—otherwise it'll cause even more
    98  	// resources to be used due the automatic (and unlimited) retrying
    99  	// as described in go.dev/issue/42699.
   100  	//
   101  	// A global timeout of 45 minutes was chosen in 2015.
   102  	// Longtest builders were added in 2018 started to reach 45 mins by 2021-2022.
   103  	// Try 2 hours next, which might last some years (depending on test volume and test speed).
   104  	return 2 * time.Hour
   105  }
   106  
   107  // isBuildlet checks the name string in order to determine if the name is for a buildlet.
   108  func isBuildlet(name string) bool {
   109  	return strings.HasPrefix(name, "buildlet-")
   110  }
   111  
   112  // TestPoolHook is used to override the buildlet returned by ForConf. It should only be used for
   113  // testing purposes.
   114  var TestPoolHook func(*dashboard.HostConfig) Buildlet
   115  
   116  // ForHost returns the appropriate buildlet depending on the host configuration that is passed it.
   117  // The returned buildlet can be overridden for testing purposes by registering a test hook.
   118  func ForHost(conf *dashboard.HostConfig) Buildlet {
   119  	if TestPoolHook != nil {
   120  		return TestPoolHook(conf)
   121  	}
   122  	if conf == nil {
   123  		panic("nil conf")
   124  	}
   125  	switch {
   126  	case conf.IsEC2:
   127  		return EC2BuildetPool()
   128  	case conf.IsVM(), conf.IsContainer():
   129  		return NewGCEConfiguration().BuildletPool()
   130  	case conf.IsReverse:
   131  		return ReversePool()
   132  	default:
   133  		panic(fmt.Sprintf("no buildlet pool for host type %q", conf.HostType))
   134  	}
   135  }