blob: 21897da46a8b7857c22c8f3c77e3cd94815d5c43 [file] [log] [blame]
Carlos Amedee87d10202020-06-02 16:56:13 -04001// 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
5package buildlet
6
7import (
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
19func 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
53func 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
173func 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 }
Carlos Amedee5a0d4622020-08-07 12:22:05 -0400193 gotErr = c.waitUntilVMExists(ctx, gotVM.ID, opts)
Carlos Amedee87d10202020-06-02 16:56:13 -0400194 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
202func 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
229func 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
286func 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
341func 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{},
Alexander Rakoczy922e3542021-10-08 17:03:24 -0400366 wantInstanceType: "e2-highcpu-2",
Carlos Amedee87d10202020-06-02 16:56:13 -0400367 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",
Alexander Rakoczy922e3542021-10-08 17:03:24 -0400393 wantInstanceType: "e2-highcpu-2",
Carlos Amedee87d10202020-06-02 16:56:13 -0400394 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}