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 }