buildlet: add an AWS buildlet client

This change adds an AWS buildlet client which allows us to
create EC2 instances on AWS. With this change we have also
moved a portion of the gce creation logic into a helper
function which allows multiple clients to use it. Metadata
for the instances are stored in the user data fields.

The creation of a buildlet pool and modifications to
rundocker buildlet be made in order to enable this change.

Updates golang/go#36841

Change-Id: Ice03e1520513d51a02b9d66542e00012453bf0d9
Reviewed-on: https://go-review.googlesource.com/c/build/+/232077
Run-TryBot: Carlos Amedee <carlos@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alexander Rakoczy <alex@golang.org>
diff --git a/buildlet/buildlet_test.go b/buildlet/buildlet_test.go
new file mode 100644
index 0000000..8bde7d3
--- /dev/null
+++ b/buildlet/buildlet_test.go
@@ -0,0 +1,143 @@
+// 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.
+
+package buildlet
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"testing"
+)
+
+func TestBuildletClient(t *testing.T) {
+	var httpCalled, OnBeginBuildletProbeCalled, OnEndBuildletProbeCalled bool
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		httpCalled = true
+		fmt.Fprintln(w, "buildlet endpoint reached")
+	}))
+	defer ts.Close()
+
+	u, err := url.Parse(ts.URL)
+	if err != nil {
+		t.Fatalf("unable to parse http server url %s", err)
+	}
+
+	kp, err := NewKeyPair()
+	if err != nil {
+		t.Fatalf("unable to create key pair %s", err)
+	}
+
+	opt := &VMOpts{
+		TLS:                  kp,
+		OnBeginBuildletProbe: func(string) { OnBeginBuildletProbeCalled = true },
+		OnEndBuildletProbe:   func(*http.Response, error) { OnEndBuildletProbeCalled = true },
+	}
+
+	gotClient, gotErr := buildletClient(context.Background(), ts.URL, u.Host, opt)
+	if gotErr != nil {
+		t.Errorf("buildletClient(ctx, %s, %s, %v) error %s", ts.URL, u.Host, opt, gotErr)
+	}
+	if gotClient == nil {
+		t.Errorf("client should not be nil")
+	}
+	if !httpCalled {
+		t.Error("http endpoint never called")
+	}
+	if !OnBeginBuildletProbeCalled {
+		t.Error("OnBeginBuildletProbe() was not called")
+	}
+	if !OnEndBuildletProbeCalled {
+		t.Error("OnEndBuildletProbe() was not called")
+	}
+}
+
+func TestBuildletClientError(t *testing.T) {
+	var httpCalled, OnBeginBuildletProbeCalled, OnEndBuildletProbeCalled bool
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		httpCalled = true
+		fmt.Fprintln(w, "buildlet endpoint reached")
+	}))
+	defer ts.Close()
+
+	u, err := url.Parse(ts.URL)
+	if err != nil {
+		t.Fatalf("unable to parse http server url %s", err)
+	}
+
+	kp, err := NewKeyPair()
+	if err != nil {
+		t.Fatalf("unable to create key pair %s", err)
+	}
+
+	opt := &VMOpts{
+		TLS:                  kp,
+		OnBeginBuildletProbe: func(string) { OnBeginBuildletProbeCalled = true },
+		OnEndBuildletProbe:   func(*http.Response, error) { OnEndBuildletProbeCalled = true },
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+	cancel()
+	gotClient, gotErr := buildletClient(ctx, ts.URL, u.Host, opt)
+	if gotErr == nil {
+		t.Errorf("buildletClient(ctx, %s, %s, %v) error %s", ts.URL, u.Host, opt, gotErr)
+	}
+	if gotClient != nil {
+		t.Errorf("client should be nil")
+	}
+	if httpCalled {
+		t.Error("http endpoint called")
+	}
+	if !OnBeginBuildletProbeCalled {
+		t.Error("OnBeginBuildletProbe() was not called")
+	}
+	if !OnEndBuildletProbeCalled {
+		t.Error("OnEndBuildletProbe() was not called")
+	}
+}
+
+func TestProbeBuildlet(t *testing.T) {
+	var httpCalled, OnBeginBuildletProbeCalled, OnEndBuildletProbeCalled bool
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		httpCalled = true
+		fmt.Fprintln(w, "buildlet endpoint reached")
+	}))
+	defer ts.Close()
+	opt := &VMOpts{
+		OnBeginBuildletProbe: func(string) { OnBeginBuildletProbeCalled = true },
+		OnEndBuildletProbe:   func(*http.Response, error) { OnEndBuildletProbeCalled = true },
+	}
+	gotErr := probeBuildlet(context.Background(), ts.URL, opt)
+	if gotErr != nil {
+		t.Errorf("probeBuildlet(ctx, %q, %+v) = %s; want no error", ts.URL, opt, gotErr)
+	}
+	if !httpCalled {
+		t.Error("http endpoint never called")
+	}
+	if !OnBeginBuildletProbeCalled {
+		t.Error("OnBeginBuildletProbe() was not called")
+	}
+	if !OnEndBuildletProbeCalled {
+		t.Error("OnEndBuildletProbe() was not called")
+	}
+}
+
+func TestProbeBuildletError(t *testing.T) {
+	var httpCalled bool
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		httpCalled = true
+		http.Error(w, "all types of broken", http.StatusInternalServerError)
+	}))
+	defer ts.Close()
+	opt := &VMOpts{}
+	gotErr := probeBuildlet(context.Background(), ts.URL, opt)
+	if gotErr == nil {
+		t.Errorf("probeBuildlet(ctx, %q, %+v) = nil; want error", ts.URL, opt)
+	}
+	if !httpCalled {
+		t.Error("http endpoint never called")
+	}
+}