internal/coordinator/pool: check if EC2 instance is a buildlet

Before destroying an untracked EC2 instance, verify that it is a
buildlet. Non buildlets will be destroyed manually as they are most
likely instances created while an operator is debugging a problem.

For golang/go#36841

Change-Id: Id0f65192ce4e72f9ecc495dd3e94750724091b51
Reviewed-on: https://go-review.googlesource.com/c/build/+/249121
Run-TryBot: Carlos Amedee <carlos@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/internal/coordinator/pool/ec2.go b/internal/coordinator/pool/ec2.go
index b97052c..26b44b7 100644
--- a/internal/coordinator/pool/ec2.go
+++ b/internal/coordinator/pool/ec2.go
@@ -341,6 +341,12 @@
 	}
 	deleteInsts := make([]string, 0, len(insts))
 	for _, inst := range insts {
+		if !isBuildlet(inst.Name) {
+			// Non-buildlets have not been created by the EC2 buildlet pool. Their lifecycle
+			// should not be managed by the pool.
+			log.Printf("destroyUntrackedInstances: skipping non-buildlet %q", inst.Name)
+			continue
+		}
 		if eb.isRemoteBuildlet(inst.Name) {
 			// Remote buildlets have their own expiration mechanism that respects active SSH sessions.
 			log.Printf("destroyUntrackedInstances: skipping remote buildlet %q", inst.Name)
diff --git a/internal/coordinator/pool/ec2_test.go b/internal/coordinator/pool/ec2_test.go
index 502b412..1436c81 100644
--- a/internal/coordinator/pool/ec2_test.go
+++ b/internal/coordinator/pool/ec2_test.go
@@ -512,11 +512,11 @@
 
 func TestEC2BuildeletDestroyUntrackedInstances(t *testing.T) {
 	awsC := cloud.NewFakeAWSClient()
-	create := func() *cloud.Instance {
+	create := func(name string) *cloud.Instance {
 		inst, err := awsC.CreateInstance(context.Background(), &cloud.EC2VMConfiguration{
 			Description: "test instance",
 			ImageID:     "image-x",
-			Name:        instanceName("host-test-type", 10),
+			Name:        name,
 			SSHKeyID:    "key-14",
 			Tags:        map[string]string{},
 			Type:        "type-x",
@@ -529,10 +529,11 @@
 	}
 	// create untracked instances
 	for it := 0; it < 10; it++ {
-		_ = create()
+		_ = create(instanceName("host-test-type", 10))
 	}
-	wantTrackedInst := create()
-	wantRemoteInst := create()
+	wantTrackedInst := create(instanceName("host-test-type", 10))
+	wantRemoteInst := create(instanceName("host-test-type", 10))
+	_ = create("debug-tiger-host-14") // non buildlet instance
 
 	pool := &EC2Buildlet{
 		awsClient: awsC,
@@ -556,9 +557,10 @@
 		},
 	}
 	pool.destroyUntrackedInstances()
+	wantInstCount := 3
 	gotInsts, err := awsC.RunningInstances(context.Background())
-	if err != nil || len(gotInsts) != 2 {
-		t.Errorf("awsClient.RunningInstances(ctx) = %+v, %s; want single inst and no error", gotInsts, err)
+	if err != nil || len(gotInsts) != wantInstCount {
+		t.Errorf("awsClient.RunningInstances(ctx) = %+v, %s; want %d instances and no error", gotInsts, err, wantInstCount)
 	}
 }
 
diff --git a/internal/coordinator/pool/gce.go b/internal/coordinator/pool/gce.go
index 2a1a21a..e2fd063 100644
--- a/internal/coordinator/pool/gce.go
+++ b/internal/coordinator/pool/gce.go
@@ -683,7 +683,7 @@
 				}
 			}
 		}
-		isBuildlet := strings.HasPrefix(inst.Name, "buildlet-")
+		isBuildlet := isBuildlet(inst.Name)
 
 		if isBuildlet && !sawDeleteAt && !p.instanceUsed(inst.Name) {
 			createdAt, _ := time.Parse(time.RFC3339Nano, inst.CreationTimestamp)