buildlet: use cloud client with AWS buildlet

Use the cloud package for AWS functions. Remove the unused
destroyVM function.

Updates golang/go#36841

Change-Id: I00e1a20c904f7c4be6460ac302085b28f518d161
Reviewed-on: https://go-review.googlesource.com/c/build/+/236300
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/ec2_test.go b/buildlet/ec2_test.go
new file mode 100644
index 0000000..8fe1ae6
--- /dev/null
+++ b/buildlet/ec2_test.go
@@ -0,0 +1,451 @@
+// 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"
+	"encoding/base64"
+	"encoding/json"
+	"testing"
+	"time"
+
+	"golang.org/x/build/buildenv"
+	"golang.org/x/build/dashboard"
+	"golang.org/x/build/internal/cloud"
+)
+
+func TestStartNewVM(t *testing.T) {
+	kp, err := NewKeyPair()
+	if err != nil {
+		t.Fatalf("unable to generate key pair: %s", err)
+	}
+	buildEnv := &buildenv.Environment{}
+	hconf := &dashboard.HostConfig{
+		VMImage: "image-x",
+	}
+	vmName := "sample-vm"
+	hostType := "host-sample-os"
+	opts := &VMOpts{
+		Zone:        "us-west",
+		ProjectID:   "project1",
+		TLS:         kp,
+		Description: "Golang builder for sample",
+		Meta: map[string]string{
+			"Owner": "george",
+		},
+		DeleteIn:                 45 * time.Second,
+		SkipEndpointVerification: true,
+	}
+	c := &EC2Client{
+		client: cloud.NewFakeAWSClient(),
+	}
+	gotClient, gotErr := c.StartNewVM(context.Background(), buildEnv, hconf, vmName, hostType, opts)
+	if gotErr != nil {
+		t.Fatalf("error is not nil: %v", gotErr)
+	}
+	if gotClient == nil {
+		t.Fatalf("response is nil")
+	}
+}
+
+func TestStartNewVMError(t *testing.T) {
+	kp, err := NewKeyPair()
+	if err != nil {
+		t.Fatalf("unable to generate key pair: %s", err)
+	}
+
+	testCases := []struct {
+		desc     string
+		buildEnv *buildenv.Environment
+		hconf    *dashboard.HostConfig
+		vmName   string
+		hostType string
+		opts     *VMOpts
+	}{
+		{
+			desc:     "nil-buildenv",
+			hconf:    &dashboard.HostConfig{},
+			vmName:   "sample-vm",
+			hostType: "host-sample-os",
+			opts: &VMOpts{
+				Zone:        "us-west",
+				ProjectID:   "project1",
+				TLS:         kp,
+				Description: "Golang builder for sample",
+				Meta: map[string]string{
+					"Owner": "george",
+				},
+				DeleteIn: 45 * time.Second,
+			},
+		},
+		{
+			desc:     "nil-hconf",
+			buildEnv: &buildenv.Environment{},
+			vmName:   "sample-vm",
+			hostType: "host-sample-os",
+			opts: &VMOpts{
+				Zone:        "us-west",
+				ProjectID:   "project1",
+				TLS:         kp,
+				Description: "Golang builder for sample",
+				Meta: map[string]string{
+					"Owner": "george",
+				},
+				DeleteIn: 45 * time.Second,
+			},
+		},
+		{
+			desc:     "empty-vnName",
+			buildEnv: &buildenv.Environment{},
+			hconf:    &dashboard.HostConfig{},
+			vmName:   "",
+			hostType: "host-sample-os",
+			opts: &VMOpts{
+				Zone:        "us-west",
+				ProjectID:   "project1",
+				TLS:         kp,
+				Description: "Golang builder for sample",
+				Meta: map[string]string{
+					"Owner": "george",
+				},
+				DeleteIn: 45 * time.Second,
+			},
+		},
+		{
+			desc:     "empty-hostType",
+			buildEnv: &buildenv.Environment{},
+			hconf:    &dashboard.HostConfig{},
+			vmName:   "sample-vm",
+			hostType: "",
+			opts: &VMOpts{
+				Zone:        "us-west",
+				ProjectID:   "project1",
+				TLS:         kp,
+				Description: "Golang builder for sample",
+				Meta: map[string]string{
+					"Owner": "george",
+				},
+				DeleteIn: 45 * time.Second,
+			},
+		},
+		{
+			desc:     "missing-certs",
+			buildEnv: &buildenv.Environment{},
+			hconf:    &dashboard.HostConfig{},
+			vmName:   "sample-vm",
+			hostType: "host-sample-os",
+			opts: &VMOpts{
+				Zone:        "us-west",
+				ProjectID:   "project1",
+				Description: "Golang builder for sample",
+				Meta: map[string]string{
+					"Owner": "george",
+				},
+				DeleteIn: 45 * time.Second,
+			},
+		},
+		{
+			desc:     "nil-opts",
+			buildEnv: &buildenv.Environment{},
+			hconf:    &dashboard.HostConfig{},
+			vmName:   "sample-vm",
+			hostType: "host-sample-os",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			c := &EC2Client{
+				client: cloud.NewFakeAWSClient(),
+			}
+			gotClient, gotErr := c.StartNewVM(context.Background(), tc.buildEnv, tc.hconf, tc.vmName, tc.hostType, tc.opts)
+			if gotErr == nil {
+				t.Errorf("StartNewVM(ctx, %+v, %+v, %s, %s, %+v) = %+v, nil; want error", tc.buildEnv, tc.hconf, tc.vmName, tc.hostType, tc.opts, gotClient)
+			}
+			if gotClient != nil {
+				t.Errorf("got %+v; expected nil", gotClient)
+			}
+		})
+	}
+}
+
+func TestWaitUntilInstanceExists(t *testing.T) {
+	vmConfig := &cloud.EC2VMConfiguration{
+		ImageID: "foo",
+		Type:    "type-a",
+		Zone:    "eu-15",
+	}
+	invoked := false
+	opts := &VMOpts{
+		OnInstanceCreated: func() {
+			invoked = true
+		},
+	}
+	ctx := context.Background()
+	c := &EC2Client{
+		client: cloud.NewFakeAWSClient(),
+	}
+	gotVM, gotErr := c.createVM(ctx, vmConfig, opts)
+	if gotErr != nil {
+		t.Fatalf("createVM(ctx, %v, %v) failed with %s", vmConfig, opts, gotErr)
+	}
+	gotErr = c.WaitUntilVMExists(ctx, gotVM.ID, opts)
+	if gotErr != nil {
+		t.Fatalf("WaitUntilVMExists(%v, %v, %v) failed with error %s", ctx, gotVM.ID, opts, gotErr)
+	}
+	if !invoked {
+		t.Errorf("OnInstanceCreated() was not invoked")
+	}
+}
+
+func TestCreateVM(t *testing.T) {
+	vmConfig := &cloud.EC2VMConfiguration{
+		ImageID: "foo",
+		Type:    "type-a",
+		Zone:    "eu-15",
+	}
+	invoked := false
+	opts := &VMOpts{
+		OnInstanceRequested: func() {
+			invoked = true
+		},
+	}
+	c := &EC2Client{
+		client: cloud.NewFakeAWSClient(),
+	}
+	gotVM, gotErr := c.createVM(context.Background(), vmConfig, opts)
+	if gotErr != nil {
+		t.Fatalf("createVM(ctx, %v, %v) failed with %s", vmConfig, opts, gotErr)
+	}
+	if gotVM.ImageID != vmConfig.ImageID || gotVM.Type != vmConfig.Type || gotVM.Zone != vmConfig.Zone {
+		t.Errorf("createVM(ctx, %+v, %+v) = %+v, nil; want vm to match config", vmConfig, opts, gotVM)
+	}
+	if !invoked {
+		t.Errorf("OnInstanceRequested() was not invoked")
+	}
+}
+
+func TestCreateVMError(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		vmConfig *cloud.EC2VMConfiguration
+		opts     *VMOpts
+	}{
+		{
+			desc: "missing-vmConfig",
+		},
+		{
+			desc: "missing-image-id",
+			vmConfig: &cloud.EC2VMConfiguration{
+				Type: "type-a",
+				Zone: "eu-15",
+			},
+			opts: &VMOpts{
+				OnInstanceRequested: func() {},
+			},
+		},
+		{
+			desc: "missing-instance-id",
+			vmConfig: &cloud.EC2VMConfiguration{
+				ImageID: "foo",
+				Zone:    "eu-15",
+			},
+			opts: &VMOpts{
+				OnInstanceRequested: func() {},
+			},
+		},
+		{
+			desc: "missing-placement",
+			vmConfig: &cloud.EC2VMConfiguration{
+				Name: "foo",
+				Type: "type-a",
+			},
+			opts: &VMOpts{
+				OnInstanceRequested: func() {},
+			},
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			c := &EC2Client{
+				client: cloud.NewFakeAWSClient(),
+				//client: &fakeAWSClient{},
+			}
+			gotVM, gotErr := c.createVM(context.Background(), tc.vmConfig, tc.opts)
+			if gotErr == nil {
+				t.Errorf("createVM(ctx, %v, %v) = %s, %v; want error", tc.vmConfig, tc.opts, gotVM.ID, gotErr)
+			}
+			if gotVM != nil {
+				t.Errorf("createVM(ctx, %v, %v) = %s, %v; %q, error", tc.vmConfig, tc.opts, gotVM.ID, gotErr, "")
+			}
+		})
+	}
+}
+
+func TestEC2BuildletParams(t *testing.T) {
+	testCases := []struct {
+		desc       string
+		inst       *cloud.Instance
+		opts       *VMOpts
+		wantURL    string
+		wantPort   string
+		wantCalled bool
+		wantErr    bool
+	}{
+		{
+			desc: "base-case",
+			inst: &cloud.Instance{
+				IPAddressExternal: "8.8.8.8",
+				IPAddressInternal: "3.3.3.3",
+			},
+			opts:       &VMOpts{},
+			wantCalled: true,
+			wantURL:    "https://8.8.8.8",
+			wantPort:   "8.8.8.8:443",
+			wantErr:    false,
+		},
+		{
+			desc: "missing-int-ip",
+			inst: &cloud.Instance{
+				IPAddressExternal: "8.8.8.8",
+			},
+			opts:       &VMOpts{},
+			wantCalled: true,
+			wantURL:    "https://8.8.8.8",
+			wantPort:   "8.8.8.8:443",
+			wantErr:    false,
+		},
+		{
+			desc: "missing-ext-ip",
+			inst: &cloud.Instance{
+				IPAddressInternal: "3.3.3.3",
+			},
+			opts:       &VMOpts{},
+			wantCalled: true,
+			wantURL:    "",
+			wantPort:   "",
+			wantErr:    true,
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			gotURL, gotPort, gotErr := ec2BuildletParams(tc.inst, tc.opts)
+			if gotURL != tc.wantURL || gotPort != tc.wantPort || tc.wantErr != (gotErr != nil) {
+				t.Errorf("ec2BuildletParams(%v, %v) = %q, %q, nil; want %q, %q, nil", tc.inst, tc.opts, gotURL, gotPort, tc.wantURL, tc.wantPort)
+			}
+		})
+	}
+}
+
+func TestConfigureVM(t *testing.T) {
+	testCases := []struct {
+		desc              string
+		buildEnv          *buildenv.Environment
+		hconf             *dashboard.HostConfig
+		hostType          string
+		opts              *VMOpts
+		vmName            string
+		wantDesc          string
+		wantImageID       string
+		wantInstanceType  string
+		wantName          string
+		wantZone          string
+		wantBuildletName  string
+		wantBuildletImage string
+	}{
+		{
+			desc:     "default-values",
+			buildEnv: &buildenv.Environment{},
+			hconf: &dashboard.HostConfig{
+				KonletVMImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
+			},
+			vmName:            "base_vm",
+			hostType:          "host-foo-bar",
+			opts:              &VMOpts{},
+			wantInstanceType:  "n1-highcpu-2",
+			wantName:          "base_vm",
+			wantBuildletName:  "base_vm",
+			wantBuildletImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
+		},
+		{
+			desc:     "full-configuration",
+			buildEnv: &buildenv.Environment{},
+			hconf: &dashboard.HostConfig{
+				VMImage:       "awesome_image",
+				KonletVMImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
+			},
+			vmName:   "base-vm",
+			hostType: "host-foo-bar",
+			opts: &VMOpts{
+				Zone: "sa-west",
+				TLS: KeyPair{
+					CertPEM: "abc",
+					KeyPEM:  "xyz",
+				},
+				Description: "test description",
+				Meta: map[string]string{
+					"sample": "value",
+				},
+			},
+			wantDesc:          "test description",
+			wantImageID:       "awesome_image",
+			wantInstanceType:  "n1-highcpu-2",
+			wantName:          "base-vm",
+			wantZone:          "sa-west",
+			wantBuildletName:  "base-vm",
+			wantBuildletImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			got := configureVM(tc.buildEnv, tc.hconf, tc.vmName, tc.hostType, tc.opts)
+			if got.ImageID != tc.wantImageID {
+				t.Errorf("ImageId got %s; want %s", got.ImageID, tc.wantImageID)
+			}
+			if got.Type != tc.wantInstanceType {
+				t.Errorf("Type got %s; want %s", got.Type, tc.wantInstanceType)
+			}
+			if got.Zone != tc.wantZone {
+				t.Errorf("Zone got %s; want %s", got.Zone, tc.wantZone)
+			}
+			if got.Name != tc.wantName {
+				t.Errorf("Name got %s; want %s", got.Name, tc.wantName)
+			}
+			if got.Description != tc.wantDesc {
+				t.Errorf("Description got %s; want %s", got.Description, tc.wantDesc)
+			}
+			gotUDJson, err := base64.StdEncoding.DecodeString(got.UserData)
+			if err != nil {
+				t.Fatalf("unable to base64 decode string %q: %s", got.UserData, err)
+			}
+			gotUD := &cloud.EC2UserData{}
+			err = json.Unmarshal([]byte(gotUDJson), gotUD)
+			if err != nil {
+				t.Errorf("unable to unmarshal user data: %v", err)
+			}
+			if gotUD.BuildletBinaryURL != tc.hconf.BuildletBinaryURL(tc.buildEnv) {
+				t.Errorf("buildletBinaryURL got %s; want %s", gotUD.BuildletBinaryURL, tc.hconf.BuildletBinaryURL(tc.buildEnv))
+			}
+			if gotUD.BuildletHostType != tc.hostType {
+				t.Errorf("buildletHostType got %s; want %s", gotUD.BuildletHostType, tc.hostType)
+			}
+			if gotUD.BuildletName != tc.wantBuildletName {
+				t.Errorf("buildletName got %s; want %s", gotUD.BuildletName, tc.wantBuildletName)
+			}
+			if gotUD.BuildletImageURL != tc.wantBuildletImage {
+				t.Errorf("buildletImageURL got %s; want %s", gotUD.BuildletImageURL, tc.wantBuildletImage)
+			}
+
+			if gotUD.TLSCert != tc.opts.TLS.CertPEM {
+				t.Errorf("TLSCert got %s; want %s", gotUD.TLSCert, tc.opts.TLS.CertPEM)
+			}
+			if gotUD.TLSKey != tc.opts.TLS.KeyPEM {
+				t.Errorf("TLSKey got %s; want %s", gotUD.TLSKey, tc.opts.TLS.KeyPEM)
+			}
+			if gotUD.TLSPassword != tc.opts.TLS.Password() {
+				t.Errorf("TLSPassword got %s; want %s", gotUD.TLSPassword, tc.opts.TLS.Password())
+			}
+		})
+	}
+}