golang.org/x/build@v0.0.0-20240506185731-218518f32b70/buildlet/ec2_test.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package buildlet
     6  
     7  import (
     8  	"context"
     9  	"encoding/base64"
    10  	"encoding/json"
    11  	"testing"
    12  	"time"
    13  
    14  	"golang.org/x/build/buildenv"
    15  	"golang.org/x/build/dashboard"
    16  	"golang.org/x/build/internal/cloud"
    17  )
    18  
    19  func TestStartNewVM(t *testing.T) {
    20  	kp, err := NewKeyPair()
    21  	if err != nil {
    22  		t.Fatalf("unable to generate key pair: %s", err)
    23  	}
    24  	buildEnv := &buildenv.Environment{}
    25  	hconf := &dashboard.HostConfig{
    26  		VMImage: "image-x",
    27  	}
    28  	vmName := "sample-vm"
    29  	hostType := "host-sample-os"
    30  	opts := &VMOpts{
    31  		Zone:        "us-west",
    32  		ProjectID:   "project1",
    33  		TLS:         kp,
    34  		Description: "Golang builder for sample",
    35  		Meta: map[string]string{
    36  			"Owner": "george",
    37  		},
    38  		DeleteIn:                 45 * time.Second,
    39  		SkipEndpointVerification: true,
    40  	}
    41  	c := &EC2Client{
    42  		client: cloud.NewFakeAWSClient(),
    43  	}
    44  	gotClient, gotErr := c.StartNewVM(context.Background(), buildEnv, hconf, vmName, hostType, opts)
    45  	if gotErr != nil {
    46  		t.Fatalf("error is not nil: %v", gotErr)
    47  	}
    48  	if gotClient == nil {
    49  		t.Fatalf("response is nil")
    50  	}
    51  }
    52  
    53  func TestStartNewVMError(t *testing.T) {
    54  	kp, err := NewKeyPair()
    55  	if err != nil {
    56  		t.Fatalf("unable to generate key pair: %s", err)
    57  	}
    58  
    59  	testCases := []struct {
    60  		desc     string
    61  		buildEnv *buildenv.Environment
    62  		hconf    *dashboard.HostConfig
    63  		vmName   string
    64  		hostType string
    65  		opts     *VMOpts
    66  	}{
    67  		{
    68  			desc:     "nil-buildenv",
    69  			hconf:    &dashboard.HostConfig{},
    70  			vmName:   "sample-vm",
    71  			hostType: "host-sample-os",
    72  			opts: &VMOpts{
    73  				Zone:        "us-west",
    74  				ProjectID:   "project1",
    75  				TLS:         kp,
    76  				Description: "Golang builder for sample",
    77  				Meta: map[string]string{
    78  					"Owner": "george",
    79  				},
    80  				DeleteIn: 45 * time.Second,
    81  			},
    82  		},
    83  		{
    84  			desc:     "nil-hconf",
    85  			buildEnv: &buildenv.Environment{},
    86  			vmName:   "sample-vm",
    87  			hostType: "host-sample-os",
    88  			opts: &VMOpts{
    89  				Zone:        "us-west",
    90  				ProjectID:   "project1",
    91  				TLS:         kp,
    92  				Description: "Golang builder for sample",
    93  				Meta: map[string]string{
    94  					"Owner": "george",
    95  				},
    96  				DeleteIn: 45 * time.Second,
    97  			},
    98  		},
    99  		{
   100  			desc:     "empty-vnName",
   101  			buildEnv: &buildenv.Environment{},
   102  			hconf:    &dashboard.HostConfig{},
   103  			vmName:   "",
   104  			hostType: "host-sample-os",
   105  			opts: &VMOpts{
   106  				Zone:        "us-west",
   107  				ProjectID:   "project1",
   108  				TLS:         kp,
   109  				Description: "Golang builder for sample",
   110  				Meta: map[string]string{
   111  					"Owner": "george",
   112  				},
   113  				DeleteIn: 45 * time.Second,
   114  			},
   115  		},
   116  		{
   117  			desc:     "empty-hostType",
   118  			buildEnv: &buildenv.Environment{},
   119  			hconf:    &dashboard.HostConfig{},
   120  			vmName:   "sample-vm",
   121  			hostType: "",
   122  			opts: &VMOpts{
   123  				Zone:        "us-west",
   124  				ProjectID:   "project1",
   125  				TLS:         kp,
   126  				Description: "Golang builder for sample",
   127  				Meta: map[string]string{
   128  					"Owner": "george",
   129  				},
   130  				DeleteIn: 45 * time.Second,
   131  			},
   132  		},
   133  		{
   134  			desc:     "missing-certs",
   135  			buildEnv: &buildenv.Environment{},
   136  			hconf:    &dashboard.HostConfig{},
   137  			vmName:   "sample-vm",
   138  			hostType: "host-sample-os",
   139  			opts: &VMOpts{
   140  				Zone:        "us-west",
   141  				ProjectID:   "project1",
   142  				Description: "Golang builder for sample",
   143  				Meta: map[string]string{
   144  					"Owner": "george",
   145  				},
   146  				DeleteIn: 45 * time.Second,
   147  			},
   148  		},
   149  		{
   150  			desc:     "nil-opts",
   151  			buildEnv: &buildenv.Environment{},
   152  			hconf:    &dashboard.HostConfig{},
   153  			vmName:   "sample-vm",
   154  			hostType: "host-sample-os",
   155  		},
   156  	}
   157  	for _, tc := range testCases {
   158  		t.Run(tc.desc, func(t *testing.T) {
   159  			c := &EC2Client{
   160  				client: cloud.NewFakeAWSClient(),
   161  			}
   162  			gotClient, gotErr := c.StartNewVM(context.Background(), tc.buildEnv, tc.hconf, tc.vmName, tc.hostType, tc.opts)
   163  			if gotErr == nil {
   164  				t.Errorf("StartNewVM(ctx, %+v, %+v, %s, %s, %+v) = %+v, nil; want error", tc.buildEnv, tc.hconf, tc.vmName, tc.hostType, tc.opts, gotClient)
   165  			}
   166  			if gotClient != nil {
   167  				t.Errorf("got %+v; expected nil", gotClient)
   168  			}
   169  		})
   170  	}
   171  }
   172  
   173  func TestWaitUntilInstanceExists(t *testing.T) {
   174  	vmConfig := &cloud.EC2VMConfiguration{
   175  		ImageID: "foo",
   176  		Type:    "type-a",
   177  		Zone:    "eu-15",
   178  	}
   179  	invoked := false
   180  	opts := &VMOpts{
   181  		OnInstanceCreated: func() {
   182  			invoked = true
   183  		},
   184  	}
   185  	ctx := context.Background()
   186  	c := &EC2Client{
   187  		client: cloud.NewFakeAWSClient(),
   188  	}
   189  	gotVM, gotErr := c.createVM(ctx, vmConfig, opts)
   190  	if gotErr != nil {
   191  		t.Fatalf("createVM(ctx, %v, %v) failed with %s", vmConfig, opts, gotErr)
   192  	}
   193  	gotErr = c.waitUntilVMExists(ctx, gotVM.ID, opts)
   194  	if gotErr != nil {
   195  		t.Fatalf("WaitUntilVMExists(%v, %v, %v) failed with error %s", ctx, gotVM.ID, opts, gotErr)
   196  	}
   197  	if !invoked {
   198  		t.Errorf("OnInstanceCreated() was not invoked")
   199  	}
   200  }
   201  
   202  func TestCreateVM(t *testing.T) {
   203  	vmConfig := &cloud.EC2VMConfiguration{
   204  		ImageID: "foo",
   205  		Type:    "type-a",
   206  		Zone:    "eu-15",
   207  	}
   208  	invoked := false
   209  	opts := &VMOpts{
   210  		OnInstanceRequested: func() {
   211  			invoked = true
   212  		},
   213  	}
   214  	c := &EC2Client{
   215  		client: cloud.NewFakeAWSClient(),
   216  	}
   217  	gotVM, gotErr := c.createVM(context.Background(), vmConfig, opts)
   218  	if gotErr != nil {
   219  		t.Fatalf("createVM(ctx, %v, %v) failed with %s", vmConfig, opts, gotErr)
   220  	}
   221  	if gotVM.ImageID != vmConfig.ImageID || gotVM.Type != vmConfig.Type || gotVM.Zone != vmConfig.Zone {
   222  		t.Errorf("createVM(ctx, %+v, %+v) = %+v, nil; want vm to match config", vmConfig, opts, gotVM)
   223  	}
   224  	if !invoked {
   225  		t.Errorf("OnInstanceRequested() was not invoked")
   226  	}
   227  }
   228  
   229  func TestCreateVMError(t *testing.T) {
   230  	testCases := []struct {
   231  		desc     string
   232  		vmConfig *cloud.EC2VMConfiguration
   233  		opts     *VMOpts
   234  	}{
   235  		{
   236  			desc: "missing-vmConfig",
   237  		},
   238  		{
   239  			desc: "missing-image-id",
   240  			vmConfig: &cloud.EC2VMConfiguration{
   241  				Type: "type-a",
   242  				Zone: "eu-15",
   243  			},
   244  			opts: &VMOpts{
   245  				OnInstanceRequested: func() {},
   246  			},
   247  		},
   248  		{
   249  			desc: "missing-instance-id",
   250  			vmConfig: &cloud.EC2VMConfiguration{
   251  				ImageID: "foo",
   252  				Zone:    "eu-15",
   253  			},
   254  			opts: &VMOpts{
   255  				OnInstanceRequested: func() {},
   256  			},
   257  		},
   258  		{
   259  			desc: "missing-placement",
   260  			vmConfig: &cloud.EC2VMConfiguration{
   261  				Name: "foo",
   262  				Type: "type-a",
   263  			},
   264  			opts: &VMOpts{
   265  				OnInstanceRequested: func() {},
   266  			},
   267  		},
   268  	}
   269  	for _, tc := range testCases {
   270  		t.Run(tc.desc, func(t *testing.T) {
   271  			c := &EC2Client{
   272  				client: cloud.NewFakeAWSClient(),
   273  				//client: &fakeAWSClient{},
   274  			}
   275  			gotVM, gotErr := c.createVM(context.Background(), tc.vmConfig, tc.opts)
   276  			if gotErr == nil {
   277  				t.Errorf("createVM(ctx, %v, %v) = %s, %v; want error", tc.vmConfig, tc.opts, gotVM.ID, gotErr)
   278  			}
   279  			if gotVM != nil {
   280  				t.Errorf("createVM(ctx, %v, %v) = %s, %v; %q, error", tc.vmConfig, tc.opts, gotVM.ID, gotErr, "")
   281  			}
   282  		})
   283  	}
   284  }
   285  
   286  func TestEC2BuildletParams(t *testing.T) {
   287  	testCases := []struct {
   288  		desc       string
   289  		inst       *cloud.Instance
   290  		opts       *VMOpts
   291  		wantURL    string
   292  		wantPort   string
   293  		wantCalled bool
   294  		wantErr    bool
   295  	}{
   296  		{
   297  			desc: "base-case",
   298  			inst: &cloud.Instance{
   299  				IPAddressExternal: "8.8.8.8",
   300  				IPAddressInternal: "3.3.3.3",
   301  			},
   302  			opts:       &VMOpts{},
   303  			wantCalled: true,
   304  			wantURL:    "https://8.8.8.8",
   305  			wantPort:   "8.8.8.8:443",
   306  			wantErr:    false,
   307  		},
   308  		{
   309  			desc: "missing-int-ip",
   310  			inst: &cloud.Instance{
   311  				IPAddressExternal: "8.8.8.8",
   312  			},
   313  			opts:       &VMOpts{},
   314  			wantCalled: true,
   315  			wantURL:    "https://8.8.8.8",
   316  			wantPort:   "8.8.8.8:443",
   317  			wantErr:    false,
   318  		},
   319  		{
   320  			desc: "missing-ext-ip",
   321  			inst: &cloud.Instance{
   322  				IPAddressInternal: "3.3.3.3",
   323  			},
   324  			opts:       &VMOpts{},
   325  			wantCalled: true,
   326  			wantURL:    "",
   327  			wantPort:   "",
   328  			wantErr:    true,
   329  		},
   330  	}
   331  	for _, tc := range testCases {
   332  		t.Run(tc.desc, func(t *testing.T) {
   333  			gotURL, gotPort, gotErr := ec2BuildletParams(tc.inst, tc.opts)
   334  			if gotURL != tc.wantURL || gotPort != tc.wantPort || tc.wantErr != (gotErr != nil) {
   335  				t.Errorf("ec2BuildletParams(%v, %v) = %q, %q, nil; want %q, %q, nil", tc.inst, tc.opts, gotURL, gotPort, tc.wantURL, tc.wantPort)
   336  			}
   337  		})
   338  	}
   339  }
   340  
   341  func TestConfigureVM(t *testing.T) {
   342  	testCases := []struct {
   343  		desc              string
   344  		buildEnv          *buildenv.Environment
   345  		hconf             *dashboard.HostConfig
   346  		hostType          string
   347  		opts              *VMOpts
   348  		vmName            string
   349  		wantDesc          string
   350  		wantImageID       string
   351  		wantInstanceType  string
   352  		wantName          string
   353  		wantZone          string
   354  		wantBuildletName  string
   355  		wantBuildletImage string
   356  	}{
   357  		{
   358  			desc:     "default-values",
   359  			buildEnv: &buildenv.Environment{},
   360  			hconf: &dashboard.HostConfig{
   361  				KonletVMImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
   362  			},
   363  			vmName:            "base_vm",
   364  			hostType:          "host-foo-bar",
   365  			opts:              &VMOpts{},
   366  			wantInstanceType:  "e2-standard-8",
   367  			wantName:          "base_vm",
   368  			wantBuildletName:  "base_vm",
   369  			wantBuildletImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
   370  		},
   371  		{
   372  			desc:     "full-configuration",
   373  			buildEnv: &buildenv.Environment{},
   374  			hconf: &dashboard.HostConfig{
   375  				VMImage:       "awesome_image",
   376  				KonletVMImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
   377  			},
   378  			vmName:   "base-vm",
   379  			hostType: "host-foo-bar",
   380  			opts: &VMOpts{
   381  				Zone: "sa-west",
   382  				TLS: KeyPair{
   383  					CertPEM: "abc",
   384  					KeyPEM:  "xyz",
   385  				},
   386  				Description: "test description",
   387  				Meta: map[string]string{
   388  					"sample": "value",
   389  				},
   390  			},
   391  			wantDesc:          "test description",
   392  			wantImageID:       "awesome_image",
   393  			wantInstanceType:  "e2-standard-8",
   394  			wantName:          "base-vm",
   395  			wantZone:          "sa-west",
   396  			wantBuildletName:  "base-vm",
   397  			wantBuildletImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
   398  		},
   399  	}
   400  	for _, tc := range testCases {
   401  		t.Run(tc.desc, func(t *testing.T) {
   402  			got := configureVM(tc.buildEnv, tc.hconf, tc.vmName, tc.hostType, tc.opts)
   403  			if got.ImageID != tc.wantImageID {
   404  				t.Errorf("ImageId got %s; want %s", got.ImageID, tc.wantImageID)
   405  			}
   406  			if got.Type != tc.wantInstanceType {
   407  				t.Errorf("Type got %s; want %s", got.Type, tc.wantInstanceType)
   408  			}
   409  			if got.Zone != tc.wantZone {
   410  				t.Errorf("Zone got %s; want %s", got.Zone, tc.wantZone)
   411  			}
   412  			if got.Name != tc.wantName {
   413  				t.Errorf("Name got %s; want %s", got.Name, tc.wantName)
   414  			}
   415  			if got.Description != tc.wantDesc {
   416  				t.Errorf("Description got %s; want %s", got.Description, tc.wantDesc)
   417  			}
   418  			gotUDJson, err := base64.StdEncoding.DecodeString(got.UserData)
   419  			if err != nil {
   420  				t.Fatalf("unable to base64 decode string %q: %s", got.UserData, err)
   421  			}
   422  			gotUD := &cloud.EC2UserData{}
   423  			err = json.Unmarshal([]byte(gotUDJson), gotUD)
   424  			if err != nil {
   425  				t.Errorf("unable to unmarshal user data: %v", err)
   426  			}
   427  			if gotUD.BuildletBinaryURL != tc.hconf.BuildletBinaryURL(tc.buildEnv) {
   428  				t.Errorf("buildletBinaryURL got %s; want %s", gotUD.BuildletBinaryURL, tc.hconf.BuildletBinaryURL(tc.buildEnv))
   429  			}
   430  			if gotUD.BuildletHostType != tc.hostType {
   431  				t.Errorf("buildletHostType got %s; want %s", gotUD.BuildletHostType, tc.hostType)
   432  			}
   433  			if gotUD.BuildletName != tc.wantBuildletName {
   434  				t.Errorf("buildletName got %s; want %s", gotUD.BuildletName, tc.wantBuildletName)
   435  			}
   436  			if gotUD.BuildletImageURL != tc.wantBuildletImage {
   437  				t.Errorf("buildletImageURL got %s; want %s", gotUD.BuildletImageURL, tc.wantBuildletImage)
   438  			}
   439  
   440  			if gotUD.TLSCert != tc.opts.TLS.CertPEM {
   441  				t.Errorf("TLSCert got %s; want %s", gotUD.TLSCert, tc.opts.TLS.CertPEM)
   442  			}
   443  			if gotUD.TLSKey != tc.opts.TLS.KeyPEM {
   444  				t.Errorf("TLSKey got %s; want %s", gotUD.TLSKey, tc.opts.TLS.KeyPEM)
   445  			}
   446  			if gotUD.TLSPassword != tc.opts.TLS.Password() {
   447  				t.Errorf("TLSPassword got %s; want %s", gotUD.TLSPassword, tc.opts.TLS.Password())
   448  			}
   449  		})
   450  	}
   451  }