| // 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: "e2-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: "e2-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()) |
| } |
| }) |
| } |
| } |