| // Copyright 2020 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build linux || darwin |
| // +build linux darwin |
| |
| package pool |
| |
| import ( |
| "context" |
| "fmt" |
| "log" |
| "math/rand" |
| "strings" |
| "time" |
| |
| "golang.org/x/build/buildlet" |
| "golang.org/x/build/dashboard" |
| "golang.org/x/build/internal/coordinator/pool/queue" |
| ) |
| |
| // Buildlet defines an interface for a pool of buildlets. |
| type Buildlet interface { |
| // GetBuildlet returns a new buildlet client. |
| // |
| // The hostType is the key into the dashboard.Hosts |
| // map (such as "host-linux-bullseye"), NOT the buidler type |
| // ("linux-386"). |
| // |
| // Users of GetBuildlet must both call Client.Close when done |
| // with the client as well as cancel the provided Context. |
| GetBuildlet(ctx context.Context, hostType string, lg Logger, item *queue.SchedItem) (buildlet.Client, error) |
| |
| String() string // TODO(bradfitz): more status stuff |
| } |
| |
| // IsRemoteBuildletFunc should report whether the buildlet instance name is |
| // is a remote buildlet. This is applicable to GCE and EC2 instances. |
| // |
| // TODO(golang.org/issue/38337): should be removed once remote buildlet management |
| // functions are moved into a package. |
| type IsRemoteBuildletFunc func(instanceName string) bool |
| |
| // randHex generates a random hex string. |
| func randHex(n int) string { |
| buf := make([]byte, n/2+1) |
| if _, err := rand.Read(buf); err != nil { |
| log.Fatalf("randHex: %v", err) |
| } |
| return fmt.Sprintf("%x", buf)[:n] |
| } |
| |
| func friendlyDuration(d time.Duration) string { |
| if d > 10*time.Second { |
| d2 := ((d + 50*time.Millisecond) / (100 * time.Millisecond)) * (100 * time.Millisecond) |
| return d2.String() |
| } |
| if d > time.Second { |
| d2 := ((d + 5*time.Millisecond) / (10 * time.Millisecond)) * (10 * time.Millisecond) |
| return d2.String() |
| } |
| d2 := ((d + 50*time.Microsecond) / (100 * time.Microsecond)) * (100 * time.Microsecond) |
| return d2.String() |
| } |
| |
| // instanceName generates a random instance name according to the host type. |
| func instanceName(hostType string, length int) string { |
| return fmt.Sprintf("buildlet-%s-rn%s", strings.TrimPrefix(hostType, "host-"), randHex(length)) |
| } |
| |
| // determineDeleteTimeout reports the buildlet delete timeout duration |
| // with the following priority: |
| // |
| // 1. Host type override from host config. |
| // 2. Global default. |
| func determineDeleteTimeout(host *dashboard.HostConfig) time.Duration { |
| if host.CustomDeleteTimeout != 0 { |
| return host.CustomDeleteTimeout |
| } |
| |
| // The value we return below is effectively a global default. |
| // |
| // The comment of CleanUpOldVMs (and CleanUpOldPodsLoop) includes: |
| // |
| // This is the safety mechanism to delete VMs which stray from the |
| // normal deleting process. VMs are created to run a single build and |
| // should be shut down by a controlling process. Due to various types |
| // of failures, they might get stranded. To prevent them from getting |
| // stranded and wasting resources forever, we instead set the |
| // "delete-at" metadata attribute on them when created to some time |
| // that's well beyond their expected lifetime. |
| // |
| // Issue go.dev/issue/52929 tracks what to do about this global |
| // timeout in the long term. Unless something changes, |
| // it needs to be maintained manually so that it's always |
| // "well beyond their expected lifetime" of each builder that doesn't |
| // otherwise override this timeout—otherwise it'll cause even more |
| // resources to be used due the automatic (and unlimited) retrying |
| // as described in go.dev/issue/42699. |
| // |
| // A global timeout of 45 minutes was chosen in 2015. |
| // Longtest builders were added in 2018 started to reach 45 mins by 2021-2022. |
| // Try 2 hours next, which might last some years (depending on test volume and test speed). |
| return 2 * time.Hour |
| } |
| |
| // isBuildlet checks the name string in order to determine if the name is for a buildlet. |
| func isBuildlet(name string) bool { |
| return strings.HasPrefix(name, "buildlet-") |
| } |
| |
| // TestPoolHook is used to override the buildlet returned by ForConf. It should only be used for |
| // testing purposes. |
| var TestPoolHook func(*dashboard.HostConfig) Buildlet |
| |
| // ForHost returns the appropriate buildlet depending on the host configuration that is passed it. |
| // The returned buildlet can be overridden for testing purposes by registering a test hook. |
| func ForHost(conf *dashboard.HostConfig) Buildlet { |
| if TestPoolHook != nil { |
| return TestPoolHook(conf) |
| } |
| if conf == nil { |
| panic("nil conf") |
| } |
| switch { |
| case conf.IsEC2(): |
| return EC2BuildetPool() |
| case conf.IsVM(), conf.IsContainer(): |
| return NewGCEConfiguration().BuildletPool() |
| case conf.IsReverse: |
| return ReversePool() |
| default: |
| panic(fmt.Sprintf("no buildlet pool for host type %q", conf.HostType)) |
| } |
| } |