github.com/GoogleCloudPlatform/compute-image-tools/cli_tools@v0.0.0-20240516224744-de2dabc4ed1b/common/imagefile/inspector_test.go (about) 1 // Copyright 2020 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package imagefile 16 17 import ( 18 "context" 19 "errors" 20 "io/ioutil" 21 "path" 22 "testing" 23 24 "github.com/stretchr/testify/assert" 25 ) 26 27 const ( 28 mountError = "mount failure" 29 inspectionError = "inspection failure" 30 ) 31 32 func TestGCSInspector_RoundBytesUp(t *testing.T) { 33 cases := []struct { 34 name string 35 actualSizeBytes, virtualSizeBytes int64 36 expectedActualGB, expectedVirtualGB int64 37 }{ 38 { 39 name: "zero doesn't round up", 40 }, 41 42 { 43 name: "whole doesn't round up", 44 actualSizeBytes: bytesPerGB * 10, 45 expectedActualGB: 10, 46 virtualSizeBytes: bytesPerGB * 8, 47 expectedVirtualGB: 8, 48 }, 49 50 { 51 name: "plus one rounds up", 52 actualSizeBytes: bytesPerGB + 1, 53 expectedActualGB: 2, 54 virtualSizeBytes: (bytesPerGB * 2) + 1, 55 expectedVirtualGB: 3, 56 }, 57 58 { 59 name: "minus one rounds up", 60 actualSizeBytes: bytesPerGB - 1, 61 expectedActualGB: 1, 62 virtualSizeBytes: (bytesPerGB * 2) - 1, 63 expectedVirtualGB: 2, 64 }, 65 } 66 67 for _, tt := range cases { 68 t.Run(tt.name, func(t *testing.T) { 69 gcsURI, client := setupClient(t, 0, 0, ImageInfo{ 70 Format: "vmdk", 71 ActualSizeBytes: tt.actualSizeBytes, 72 VirtualSizeBytes: tt.virtualSizeBytes, 73 }) 74 ctx := context.Background() 75 result, err := client.Inspect(ctx, gcsURI) 76 assert.NoError(t, err) 77 assert.Equal(t, tt.expectedActualGB, result.PhysicalSizeGB) 78 assert.Equal(t, tt.expectedVirtualGB, result.VirtualSizeGB) 79 }) 80 } 81 } 82 83 func TestGCSInspector_DontRetryMount_IfContextCancelled(t *testing.T) { 84 mountFailures := 1 85 gcsURI, client := setupClient(t, mountFailures, 0, ImageInfo{ 86 Format: "vmdk", 87 ActualSizeBytes: 1024, 88 VirtualSizeBytes: 1024, 89 }) 90 ctx, cancel := context.WithCancel(context.Background()) 91 cancel() 92 _, err := client.Inspect(ctx, gcsURI) 93 assert.EqualError(t, err, mountError) 94 } 95 96 func TestGCSInspector_DontRetryInspect_IfContextCancelled(t *testing.T) { 97 inspectFailures := 1 98 gcsURI, client := setupClient(t, 0, inspectFailures, ImageInfo{ 99 Format: "vmdk", 100 ActualSizeBytes: 1024, 101 VirtualSizeBytes: 1024, 102 }) 103 ctx, cancel := context.WithCancel(context.Background()) 104 cancel() 105 _, err := client.Inspect(ctx, gcsURI) 106 assert.EqualError(t, err, inspectionError) 107 } 108 109 func TestGCSInspector_PerformRetry_WhenMountingFails(t *testing.T) { 110 // Fail mounting three times, and then successfully mount. 111 mountFailures := 3 112 gcsURI, client := setupClient(t, mountFailures, 0, ImageInfo{ 113 Format: "vmdk", 114 ActualSizeBytes: 1024, 115 VirtualSizeBytes: 1024, 116 }) 117 _, err := client.Inspect(context.Background(), gcsURI) 118 assert.NoError(t, err) 119 } 120 121 func TestGCSInspector_PerformRetry_WhenInspectionFails(t *testing.T) { 122 // Fail inspection three times, and then successfully inspect. 123 inspectFailures := 3 124 gcsURI, client := setupClient(t, 0, inspectFailures, ImageInfo{ 125 Format: "vmdk", 126 ActualSizeBytes: 1024, 127 VirtualSizeBytes: 1024, 128 }) 129 _, err := client.Inspect(context.Background(), gcsURI) 130 assert.NoError(t, err) 131 } 132 133 func setupClient(t *testing.T, mountFailures, inspectFailures int, qemuResult ImageInfo) ( 134 string, Inspector) { 135 pathToFakeMount, err := ioutil.TempFile("", "") 136 assert.NoError(t, err) 137 defer pathToFakeMount.Close() 138 assert.NoError(t, err) 139 objectName := path.Base(pathToFakeMount.Name()) 140 fakeMountDir := path.Dir(pathToFakeMount.Name()) 141 gcsURI := "gs://bucket/" + objectName 142 143 inspector := NewGCSInspector().(gcsInspector) 144 inspector.fuseClient = &mockGCSFuse{ 145 failuresRemaining: mountFailures, 146 expectedBucket: "bucket", 147 t: t, 148 returnValue: fakeMountDir, 149 } 150 inspector.qemuClient = &mockQemuClient{ 151 failuresRemaining: inspectFailures, 152 expectedFilename: pathToFakeMount.Name(), 153 t: t, 154 returnValue: qemuResult, 155 } 156 return gcsURI, inspector 157 } 158 159 type mockGCSFuse struct { 160 failuresRemaining int 161 expectedBucket string 162 t *testing.T 163 returnValue string 164 } 165 166 func (m *mockGCSFuse) MountToTemp(ctx context.Context, bucket string) (string, error) { 167 assert.Equal(m.t, m.expectedBucket, bucket) 168 if m.failuresRemaining > 0 { 169 m.failuresRemaining-- 170 err := errors.New(mountError) 171 m.t.Logf("gcsfuse returning %v", err) 172 return "", err 173 } 174 return m.returnValue, nil 175 } 176 177 func (m *mockGCSFuse) Unmount(directory string) error { 178 return nil 179 } 180 181 type mockQemuClient struct { 182 failuresRemaining int 183 expectedFilename string 184 t *testing.T 185 returnValue ImageInfo 186 } 187 188 func (m *mockQemuClient) GetInfo(ctx context.Context, filename string) (ImageInfo, error) { 189 assert.Equal(m.t, m.expectedFilename, filename) 190 if m.failuresRemaining > 0 { 191 m.failuresRemaining-- 192 err := errors.New(inspectionError) 193 m.t.Logf("qemu-img returning %v", err) 194 return ImageInfo{}, err 195 } 196 return m.returnValue, nil 197 }