Skip to content

Commit 50f86f2

Browse files
committed
create a volume downloader and use it in cloudinit instead of custom code
1 parent 1f5f4ac commit 50f86f2

File tree

7 files changed

+71
-56
lines changed

7 files changed

+71
-56
lines changed

libvirt/cloudinit_def.go

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package libvirt
22

33
import (
4-
"bufio"
54
"context"
65
"encoding/xml"
76
"errors"
@@ -109,7 +108,7 @@ func (ci *defCloudInit) UploadIso(ctx context.Context, client *Client, iso strin
109108
}
110109

111110
// upload ISO file
112-
err = img.Import(newCopier(virConn, &volume, uint64(size)), volumeDef)
111+
err = img.Import(newVolumeUploader(virConn, &volume, uint64(size)), volumeDef)
113112
if err != nil {
114113
return "", fmt.Errorf("error while uploading cloudinit %s: %w", img.String(), err)
115114
}
@@ -301,36 +300,16 @@ func readIso9660File(file os.FileInfo) ([]byte, error) {
301300
// Returns a pointer to the ISO file. Note well: you have to close this file
302301
// pointer when you are done.
303302
func downloadISO(virConn *libvirt.Libvirt, volume libvirt.StorageVol) (*os.File, error) {
304-
// get Volume info (required to get size later)
305-
_, size, _, err := virConn.StorageVolGetInfo(volume)
306-
if err != nil {
307-
return nil, fmt.Errorf("error retrieving info for volume: %w", err)
308-
}
309-
310-
// create tmp file for the ISO
311303
tmpFile, err := os.CreateTemp("", "cloudinit")
312304
if err != nil {
313305
return nil, fmt.Errorf("cannot create tmp file: %w", err)
314306
}
315307

316-
w := bufio.NewWriterSize(tmpFile, int(size))
317-
318-
// download ISO file
319-
if err := virConn.StorageVolDownload(volume, w, 0, size, 0); err != nil {
308+
downloader := newVolumeDownloader(virConn, &volume)
309+
if err := downloader(tmpFile); err != nil {
320310
return tmpFile, fmt.Errorf("error while downloading volume: %w", err)
321311
}
322312

323-
bytesCopied := w.Buffered()
324-
err = w.Flush()
325-
if err != nil {
326-
return tmpFile, fmt.Errorf("error while copying remote volume to local disk: %w", err)
327-
}
328-
329-
log.Printf("%d bytes downloaded", bytesCopied)
330-
if uint64(bytesCopied) != size {
331-
return tmpFile, fmt.Errorf("error while copying remote volume to local disk, bytesCopied %d != %d volume.size", bytesCopied, size)
332-
}
333-
334313
if _, err := tmpFile.Seek(0, 0); err != nil {
335314
return nil, err
336315
}

libvirt/coreos_ignition_def.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func (ign *defIgnition) CreateAndUpload(ctx context.Context, client *Client) (st
9898
}
9999

100100
// upload ignition file
101-
err = img.Import(newCopier(virConn, &volume, volumeDef.Capacity.Value), volumeDef)
101+
err = img.Import(newVolumeUploader(virConn, &volume, volumeDef.Capacity.Value), volumeDef)
102102
if err != nil {
103103
return "", fmt.Errorf("error while uploading ignition file %s: %w", img.String(), err)
104104
}

libvirt/resource_libvirt_volume.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ be smaller than the backing store specified with
256256

257257
// upload source if present
258258
if _, ok := d.GetOk("source"); ok {
259-
err = img.Import(newCopier(virConn, &volume, volumeDef.Capacity.Value), volumeDef)
259+
err = img.Import(newVolumeUploader(virConn, &volume, volumeDef.Capacity.Value), volumeDef)
260260
if err != nil {
261261
// don't save volume ID in case of error. This will taint the volume after.
262262
// If we don't throw away the id, we will keep instead a broken volume.

libvirt/utils_volume.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,36 @@ import (
1313
)
1414

1515
const (
16-
copierBufferSize = 4 * 1024 * 1024
16+
uploaderBufferSize = 4 * 1024 * 1024
1717
)
1818

19-
func newCopier(virConn *libvirt.Libvirt, volume *libvirt.StorageVol, size uint64) func(src io.Reader) error {
20-
copier := func(src io.Reader) error {
19+
func newVolumeUploader(virConn *libvirt.Libvirt, volume *libvirt.StorageVol, size uint64) func(src io.Reader) error {
20+
return func(src io.Reader) error {
2121
start := time.Now()
22-
if err := virConn.StorageVolUpload(*volume, bufio.NewReaderSize(src, copierBufferSize), 0, size, 0); err != nil {
22+
if err := virConn.StorageVolUpload(*volume, bufio.NewReaderSize(src, uploaderBufferSize), 0, size, 0); err != nil {
2323
return fmt.Errorf("error while uploading volume %w", err)
2424
}
2525
log.Printf("[DEBUG] upload took %d ms", time.Since(start).Milliseconds())
2626

2727
return nil
2828
}
29-
return copier
29+
}
30+
31+
// returns a function you can give a writer to download the volume content
32+
// the function will return the downloaded size
33+
func newVolumeDownloader(virConn *libvirt.Libvirt, volume *libvirt.StorageVol) func(src io.Writer) error {
34+
return func(dst io.Writer) error {
35+
start := time.Now()
36+
37+
bufdst := bufio.NewWriter(dst)
38+
if err := virConn.StorageVolDownload(*volume, bufdst, 0, 0, 0); err != nil {
39+
return fmt.Errorf("error while downloading volume: %w", err)
40+
}
41+
42+
log.Printf("[DEBUG] download took %d ms", time.Since(start).Milliseconds())
43+
44+
return bufdst.Flush()
45+
}
3046
}
3147

3248
//nolint:mnd

libvirt/utils_volume_test.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
package libvirt
22

33
import (
4+
"crypto/sha256"
45
"fmt"
6+
"io"
57
"os"
8+
"path/filepath"
69
"testing"
710
"time"
811

912
libvirt "github.com/digitalocean/go-libvirt"
1013
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
1114
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
15+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
16+
"github.com/stretchr/testify/assert"
17+
"github.com/stretchr/testify/require"
1218
)
1319

1420
func TestTimeFromEpoch(t *testing.T) {
@@ -29,27 +35,17 @@ func TestTimeFromEpoch(t *testing.T) {
2935
}
3036
}
3137

32-
func TestAccUtilsVolume_UploadVolumeCopier(t *testing.T) {
38+
func TestAccUtilsVolume_UploaderDownloader(t *testing.T) {
3339
var volume libvirt.StorageVol
3440
randomVolumeResource := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)
3541
randomVolumeName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)
3642
randomPoolName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)
3743
randomPoolPath := t.TempDir()
3844

39-
tmpfile, err := os.CreateTemp(t.TempDir(), "test-image-")
40-
if err != nil {
41-
t.Fatal(err)
42-
}
43-
44-
// simulate uploading a 1G file usig a sparse file
45-
if err := tmpfile.Truncate(1024 * 1024 * 1024); err != nil {
46-
t.Fatal(err)
47-
}
45+
imagePath, err := filepath.Abs("testdata/test.qcow2")
46+
require.NoError(t, err)
4847

49-
defer os.Remove(tmpfile.Name())
50-
defer tmpfile.Close()
51-
52-
url := fmt.Sprintf("file://%s", tmpfile.Name())
48+
url := fmt.Sprintf("file://%s", imagePath)
5349

5450
config := fmt.Sprintf(`
5551
resource "libvirt_pool" "%s" {
@@ -74,8 +70,32 @@ func TestAccUtilsVolume_UploadVolumeCopier(t *testing.T) {
7470
testAccCheckLibvirtVolumeExists("libvirt_volume."+randomVolumeResource, &volume),
7571
resource.TestCheckResourceAttr(
7672
"libvirt_volume."+randomVolumeResource, "name", randomVolumeName),
73+
func(state *terraform.State) error {
74+
virConn := testAccProvider.Meta().(*Client).libvirt
75+
76+
file, err := os.CreateTemp("", "downloader-")
77+
require.NoError(t, err)
78+
79+
defer os.Remove(file.Name())
80+
defer file.Close()
81+
82+
downloader := newVolumeDownloader(virConn, &volume)
83+
err = downloader(file)
84+
require.NoError(t, err)
85+
86+
_, err = file.Seek(0, 0)
87+
require.NoError(t, err)
88+
89+
h := sha256.New()
90+
_, err = io.Copy(h, file)
91+
require.NoError(t, err)
92+
93+
assert.Equal(t, "0f71acdc66da59b04121b939573bec2e5be78a6cdf829b64142cf0a93a7076f5", fmt.Sprintf("%x", h.Sum(nil)))
94+
return nil
95+
},
7796
),
7897
},
7998
},
8099
})
100+
81101
}

libvirt/volume_image.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (i *localImage) IsQCOW2() (bool, error) {
6161
return isQCOW2Header(buf)
6262
}
6363

64-
func (i *localImage) Import(copier func(io.Reader) error, vol libvirtxml.StorageVolume) error {
64+
func (i *localImage) Import(uploader func(io.Reader) error, vol libvirtxml.StorageVolume) error {
6565
file, err := os.Open(i.path)
6666
defer file.Close()
6767
if err != nil {
@@ -80,7 +80,7 @@ func (i *localImage) Import(copier func(io.Reader) error, vol libvirtxml.Storage
8080
}
8181
}
8282

83-
return copier(file)
83+
return uploader(file)
8484
}
8585

8686
type httpImage struct {
@@ -158,7 +158,7 @@ func (i *httpImage) IsQCOW2() (bool, error) {
158158
return isQCOW2Header(header)
159159
}
160160

161-
func (i *httpImage) Import(copier func(io.Reader) error, vol libvirtxml.StorageVolume) error {
161+
func (i *httpImage) Import(uploader func(io.Reader) error, vol libvirtxml.StorageVolume) error {
162162
// number of download retries on non client errors (eg. 5xx)
163163
const maxHTTPRetries int = 3
164164
// wait time between retries
@@ -187,7 +187,7 @@ func (i *httpImage) Import(copier func(io.Reader) error, vol libvirtxml.StorageV
187187
if response.StatusCode == http.StatusNotModified {
188188
return nil
189189
} else if response.StatusCode == http.StatusOK {
190-
return copier(response.Body)
190+
return uploader(response.Body)
191191
} else if response.StatusCode < http.StatusInternalServerError {
192192
break
193193
} else if retryCount < maxHTTPRetries {

libvirt/volume_image_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,12 @@ func TestLocalImageDownload(t *testing.T) {
123123
Mtime: fmt.Sprintf("%d.%d", tmpfileStat.ModTime().Unix(), tmpfileStat.ModTime().Nanosecond()),
124124
}
125125

126-
copier := func(r io.Reader) error {
126+
uploader := func(r io.Reader) error {
127127
require.FailNow(t, fmt.Sprintf("This should not be run, as image has not changed. url: %s", url))
128128
return nil
129129
}
130130

131-
if err = image.Import(copier, vol); err != nil {
131+
if err = image.Import(uploader, vol); err != nil {
132132
require.NoError(t, err, "As the image was not modified and not copied, no error was expected. url: %s", tmpfile.Name())
133133
}
134134

@@ -156,7 +156,7 @@ func TestRemoteImageDownloadRetry(t *testing.T) {
156156
}))
157157
}
158158

159-
copier := func(r io.Reader) error {
159+
uploader := func(r io.Reader) error {
160160
_, err := io.ReadAll(r)
161161
return err
162162
}
@@ -169,7 +169,7 @@ func TestRemoteImageDownloadRetry(t *testing.T) {
169169
t.Errorf("Could not create image object: %v", err)
170170
}
171171
start := time.Now()
172-
if err = image.Import(copier, vol); err != nil {
172+
if err = image.Import(uploader, vol); err != nil {
173173
t.Fatalf("Expected to retry: %v", err)
174174
}
175175
if time.Since(start).Seconds() < 4 {
@@ -184,7 +184,7 @@ func TestRemoteImageDownloadRetry(t *testing.T) {
184184
if err != nil {
185185
t.Errorf("Could not create image object: %v", err)
186186
}
187-
if err = image.Import(copier, vol); err == nil {
187+
if err = image.Import(uploader, vol); err == nil {
188188
t.Fatalf("Expected %s to fail with status 4xx", server.URL)
189189
}
190190
if time.Since(start).Seconds() < 2 {
@@ -219,11 +219,11 @@ func TestRemoteImageDownload(t *testing.T) {
219219
vol.Target.Timestamps = &libvirtxml.StorageVolumeTargetTimestamps{
220220
Mtime: fmt.Sprintf("%d.%d", tmpfileStat.ModTime().Unix(), tmpfileStat.ModTime().Nanosecond()),
221221
}
222-
copier := func(r io.Reader) error {
222+
uploader := func(r io.Reader) error {
223223
t.Fatalf("ERROR: starting copy of %s... but the file is the same!", url)
224224
return nil
225225
}
226-
if err = image.Import(copier, vol); err != nil {
226+
if err = image.Import(uploader, vol); err != nil {
227227
t.Fatalf("Could not copy image from %s: %v", url, err)
228228
}
229229
t.Log("File not copied because modification time was the same")

0 commit comments

Comments
 (0)