From 81f7527f3597740b67d0430534e12ee0a1da1713 Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Wed, 15 Mar 2017 12:53:28 +0100 Subject: Use If-Modified-Since for downloading images --- libvirt/cloudinit_def.go | 22 ++-- libvirt/resource_libvirt_volume.go | 185 ++++++++++---------------------- libvirt/resource_libvirt_volume_test.go | 92 +++++++++++----- libvirt/utils_net.go | 65 ++++++++++- libvirt/utils_volume.go | 120 +++++++++++++++++++++ libvirt/utils_volume_test.go | 88 +++++++++++++++ libvirt/volume_def.go | 65 ++++++++++- libvirt/volume_def_test.go | 48 +++++++++ 8 files changed, 523 insertions(+), 162 deletions(-) create mode 100644 libvirt/utils_volume.go create mode 100644 libvirt/utils_volume_test.go diff --git a/libvirt/cloudinit_def.go b/libvirt/cloudinit_def.go index a159d759..bdba94bc 100644 --- a/libvirt/cloudinit_def.go +++ b/libvirt/cloudinit_def.go @@ -108,14 +108,24 @@ func (ci *defCloudInit) CreateAndUpload(virConn *libvirt.VirConnection) (string, defer volume.Free() // upload ISO file - stream, err := libvirt.NewVirStream(virConn, 0) - if err != nil { - return "", err + copier := func(src io.Reader) error { + stream, err := libvirt.NewVirStream(virConn, 0) + if err != nil { + return err + } + defer stream.Close() + + volume.Upload(stream, 0, uint64(size), 0) + + n, err := io.Copy(stream, src) + if err != nil { + return fmt.Errorf("Error while downloading %s: %s", img.String(), err) + } + log.Printf("%d bytes uploaded\n", n) + return nil } - defer stream.Close() - volume.Upload(stream, 0, uint64(volumeDef.Capacity.Amount), 0) - err = img.WriteToStream(stream) + err = img.Import(copier, volumeDef) if err != nil { return "", err } diff --git a/libvirt/resource_libvirt_volume.go b/libvirt/resource_libvirt_volume.go index c2042ddb..5c0337b2 100644 --- a/libvirt/resource_libvirt_volume.go +++ b/libvirt/resource_libvirt_volume.go @@ -6,108 +6,12 @@ import ( "io" "log" "net/http" - "net/url" - "os" "strconv" - "strings" libvirt "github.com/dmacvicar/libvirt-go" "github.com/hashicorp/terraform/helper/schema" ) -// network transparent image -type image interface { - Size() (int64, error) - WriteToStream(*libvirt.VirStream) error - String() string -} - -type localImage struct { - path string -} - -func (i *localImage) String() string { - return i.path -} - -func (i *localImage) Size() (int64, error) { - file, err := os.Open(i.path) - if err != nil { - return 0, err - } - - fi, err := file.Stat() - if err != nil { - return 0, err - } - return fi.Size(), nil -} - -func (i *localImage) WriteToStream(stream *libvirt.VirStream) error { - file, err := os.Open(i.path) - defer file.Close() - if err != nil { - return fmt.Errorf("Error while opening %s: %s", i.path, err) - } - - n, err := io.Copy(stream, file) - if err != nil { - return fmt.Errorf("Error while reading %s: %s", i.path, err) - } - log.Printf("%d bytes uploaded\n", n) - return nil -} - -type httpImage struct { - url *url.URL -} - -func (i *httpImage) String() string { - return i.url.String() -} - -func (i *httpImage) Size() (int64, error) { - response, err := http.Head(i.url.String()) - if err != nil { - return 0, err - } - length, err := strconv.Atoi(response.Header.Get("Content-Length")) - if err != nil { - return 0, err - } - return int64(length), nil -} - -func (i *httpImage) WriteToStream(stream *libvirt.VirStream) error { - response, err := http.Get(i.url.String()) - defer response.Body.Close() - if err != nil { - return fmt.Errorf("Error while downloading %s: %s", i.url.String(), err) - } - - n, err := io.Copy(stream, response.Body) - if err != nil { - return fmt.Errorf("Error while downloading %s: %s", i.url.String(), err) - } - log.Printf("%d bytes uploaded\n", n) - return nil -} - -func newImage(source string) (image, error) { - url, err := url.Parse(source) - if err != nil { - return nil, fmt.Errorf("Can't parse source '%s' as url: %s", source, err) - } - - if strings.HasPrefix(url.Scheme, "http") { - return &httpImage{url: url}, nil - } else if url.Scheme == "file" || url.Scheme == "" { - return &localImage{path: url.Path}, nil - } else { - return nil, fmt.Errorf("Don't know how to read from '%s': %s", url.String(), err) - } -} - func volumeCommonSchema() map[string]*schema.Schema { return map[string]*schema.Schema{ "name": &schema.Schema{ @@ -203,8 +107,13 @@ func resourceLibvirtVolumeCreate(d *schema.ResourceData, meta interface{}) error volumeDef.Name = name.(string) } - // an existing image was given, this mean we can't choose size - if url, ok := d.GetOk("source"); ok { + var ( + img image + volume *libvirt.VirStorageVol = nil + ) + + // an source image was given, this mean we can't choose size + if source, ok := d.GetOk("source"); ok { // source and size conflict if _, ok := d.GetOk("size"); ok { return fmt.Errorf("'size' can't be specified when also 'source' is given (the size will be set to the size of the source image.") @@ -217,19 +126,31 @@ func resourceLibvirtVolumeCreate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("'base_volume_name' can't be specified when also 'source' is given.") } - img, err := newImage(url.(string)) - if err != nil { - return err + // Check if we already have this image in the pool + if len(volumeDef.Name) > 0 { + if v, err := pool.LookupStorageVolByName(volumeDef.Name); err != nil { + log.Printf("Could not find image %s in pool %s", volumeDef.Name, poolName) + } else { + volume = &v + volumeDef, err = newDefVolumeFromLibvirt(volume) + if err != nil { + return fmt.Errorf("could not get a volume definition from XML for %s: %s.", volumeDef.Name, err) + } + } } - size, err := img.Size() - if err != nil { + if img, err = newImage(source.(string)); err != nil { return err } - log.Printf("Image %s image is: %d bytes", img, size) - volumeDef.Capacity.Unit = "B" - volumeDef.Capacity.Amount = size + // update the image in the description, even if the file has not changed + if size, err := img.Size(); err != nil { + return err + } else { + log.Printf("Image %s image is: %d bytes", img, size) + volumeDef.Capacity.Unit = "B" + volumeDef.Capacity.Amount = size + } } else { _, noSize := d.GetOk("size") _, noBaseVol := d.GetOk("base_volume_id") @@ -237,7 +158,7 @@ func resourceLibvirtVolumeCreate(d *schema.ResourceData, meta interface{}) error if noSize && noBaseVol { return fmt.Errorf("'size' needs to be specified if no 'source' or 'base_volume_id' is given.") } - volumeDef.Capacity.Amount = int64(d.Get("size").(int)) + volumeDef.Capacity.Amount = uint64(d.Get("size").(int)) } if baseVolumeId, ok := d.GetOk("base_volume_id"); ok { @@ -249,6 +170,7 @@ func resourceLibvirtVolumeCreate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("'base_volume_name' can't be specified when also 'base_volume_id' is given.") } + volume = nil volumeDef.BackingStore = new(defBackingStore) volumeDef.BackingStore.Format.Type = "qcow2" baseVolume, err := virConn.LookupStorageVolByKey(baseVolumeId.(string)) @@ -267,6 +189,7 @@ func resourceLibvirtVolumeCreate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("'size' can't be specified when also 'base_volume_name' is given (the size will be set to the size of the backing image.") } + volume = nil baseVolumePool := pool if _, ok := d.GetOk("base_volume_pool"); ok { baseVolumePoolName := d.Get("base_volume_pool").(string) @@ -290,17 +213,20 @@ func resourceLibvirtVolumeCreate(d *schema.ResourceData, meta interface{}) error volumeDef.BackingStore.Path = baseVolPath } - volumeDefXml, err := xml.Marshal(volumeDef) - if err != nil { - return fmt.Errorf("Error serializing libvirt volume: %s", err) - } + if volume == nil { + volumeDefXml, err := xml.Marshal(volumeDef) + if err != nil { + return fmt.Errorf("Error serializing libvirt volume: %s", err) + } - // create the volume - volume, err := pool.StorageVolCreateXML(string(volumeDefXml), 0) - if err != nil { - return fmt.Errorf("Error creating libvirt volume: %s", err) + // create the volume + v, err := pool.StorageVolCreateXML(string(volumeDefXml), 0) + if err != nil { + return fmt.Errorf("Error creating libvirt volume: %s", err) + } + volume = &v + defer volume.Free() } - defer volume.Free() // we use the key as the id key, err := volume.GetKey() @@ -318,20 +244,25 @@ func resourceLibvirtVolumeCreate(d *schema.ResourceData, meta interface{}) error log.Printf("[INFO] Volume ID: %s", d.Id()) // upload source if present - if source, ok := d.GetOk("source"); ok { - stream, err := libvirt.NewVirStream(virConn, 0) - if err != nil { - return err - } - defer stream.Close() + if _, ok := d.GetOk("source"); ok { + copier := func(src io.Reader) error { + stream, err := libvirt.NewVirStream(virConn, 0) + if err != nil { + return err + } + defer stream.Close() - img, err := newImage(source.(string)) - if err != nil { - return err + volume.Upload(stream, 0, volumeDef.Capacity.Amount, 0) + + n, err := io.Copy(stream, src) + if err != nil { + return fmt.Errorf("Error while downloading %s: %s", img.String(), err) + } + log.Printf("%d bytes uploaded\n", n) + return nil } - volume.Upload(stream, 0, uint64(volumeDef.Capacity.Amount), 0) - err = img.WriteToStream(stream) + err = img.Import(copier, volumeDef) if err != nil { return err } diff --git a/libvirt/resource_libvirt_volume_test.go b/libvirt/resource_libvirt_volume_test.go index 70424e06..03be51a4 100644 --- a/libvirt/resource_libvirt_volume_test.go +++ b/libvirt/resource_libvirt_volume_test.go @@ -9,28 +9,6 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccLibvirtVolume_Basic(t *testing.T) { - var volume libvirt.VirStorageVol - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckLibvirtVolumeDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccCheckLibvirtVolumeConfig_basic, - Check: resource.ComposeTestCheckFunc( - testAccCheckLibvirtVolumeExists("libvirt_volume.terraform-acceptance-test-1", &volume), - resource.TestCheckResourceAttr( - "libvirt_volume.terraform-acceptance-test-1", "name", "terraform-test"), - resource.TestCheckResourceAttr( - "libvirt_volume.terraform-acceptance-test-1", "size", "1073741824"), - ), - }, - }, - }) -} - func testAccCheckLibvirtVolumeDestroy(s *terraform.State) error { virConn := testAccProvider.Meta().(*Client).libvirt @@ -104,9 +82,69 @@ func testAccCheckLibvirtVolumeDoesNotExists(n string, volume *libvirt.VirStorage } } -const testAccCheckLibvirtVolumeConfig_basic = ` -resource "libvirt_volume" "terraform-acceptance-test-1" { - name = "terraform-test" - size = 1073741824 +func TestAccLibvirtVolume_Basic(t *testing.T) { + var volume libvirt.VirStorageVol + + const testAccCheckLibvirtVolumeConfig_basic = ` + resource "libvirt_volume" "terraform-acceptance-test-1" { + name = "terraform-test" + size = 1073741824 + }` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibvirtVolumeDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckLibvirtVolumeConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLibvirtVolumeExists("libvirt_volume.terraform-acceptance-test-1", &volume), + resource.TestCheckResourceAttr( + "libvirt_volume.terraform-acceptance-test-1", "name", "terraform-test"), + resource.TestCheckResourceAttr( + "libvirt_volume.terraform-acceptance-test-1", "size", "1073741824"), + ), + }, + }, + }) +} + +func TestAccLibvirtVolume_DownloadFromSource(t *testing.T) { + var volume libvirt.VirStorageVol + + fws := fileWebServer{} + if err := fws.Start(); err != nil { + t.Fatal(err) + } + defer fws.Stop() + + content := []byte("this is a qcow image... well, it is not") + url, _, err := fws.AddFile(content) + if err != nil { + t.Fatal(err) + } + + const testAccCheckLibvirtVolumeConfig_source = ` + resource "libvirt_volume" "terraform-acceptance-test-2" { + name = "terraform-test" + source = "%s" + }` + config := fmt.Sprintf(testAccCheckLibvirtVolumeConfig_source, url) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibvirtVolumeDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: config, + Check: resource.ComposeTestCheckFunc( + testAccCheckLibvirtVolumeExists("libvirt_volume.terraform-acceptance-test-2", &volume), + resource.TestCheckResourceAttr( + "libvirt_volume.terraform-acceptance-test-2", "name", "terraform-test"), + ), + }, + }, + }) } -` diff --git a/libvirt/utils_net.go b/libvirt/utils_net.go index 8f96d6a3..efb0d725 100644 --- a/libvirt/utils_net.go +++ b/libvirt/utils_net.go @@ -1,9 +1,14 @@ package libvirt import ( - "crypto/rand" "fmt" + "io/ioutil" + "math/rand" "net" + "net/http" + "os" + "path" + "time" ) const ( @@ -31,6 +36,14 @@ func RandomMACAddress() (string, error) { buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]), nil } +func RandomPort() int { + const minPort = 1024 + const maxPort = 65535 + + rand.Seed(time.Now().Unix()) + return rand.Intn(maxPort-minPort) + minPort +} + func FreeNetworkInterface(basename string) (string, error) { for i := 0; i < maxIfaceNum; i++ { ifaceName := fmt.Sprintf("%s%d", basename, i) @@ -56,3 +69,53 @@ func NetworkRange(network *net.IPNet) (net.IP, net.IP) { } return firstIP, lastIP } + +// a HTTP server that serves files in a directory, used mostly for testing +type fileWebServer struct { + Dir string + Port int + Url string + + server *http.Server +} + +func (fws *fileWebServer) Start() error { + dir, err := ioutil.TempDir(fws.Dir, "") + if err != nil { + return err + } + + fws.Dir = dir + fws.Port = RandomPort() + fws.Url = fmt.Sprintf("http://127.0.0.1:%d", fws.Port) + + handler := http.NewServeMux() + handler.Handle("/", http.FileServer(http.Dir(dir))) + fws.server = &http.Server{Addr: fmt.Sprintf(":%d", fws.Port), Handler: handler} + ln, err := net.Listen("tcp", fws.server.Addr) + if err != nil { + return err + } + go fws.server.Serve(ln) + return nil +} + +// Adds a file (with some content) in the directory served by the fileWebServer +func (fws *fileWebServer) AddFile(content []byte) (string, *os.File, error) { + tmpfile, err := ioutil.TempFile(fws.Dir, "file-") + if err != nil { + return "", nil, err + } + + if len(content) > 0 { + if _, err := tmpfile.Write(content); err != nil { + return "", nil, err + } + } + + return fmt.Sprintf("%s/%s", fws.Url, path.Base(tmpfile.Name())), tmpfile, nil +} + +func (fws *fileWebServer) Stop() { + os.RemoveAll(fws.Dir) +} diff --git a/libvirt/utils_volume.go b/libvirt/utils_volume.go new file mode 100644 index 00000000..6793124c --- /dev/null +++ b/libvirt/utils_volume.go @@ -0,0 +1,120 @@ +package libvirt + +import ( + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "strconv" + "strings" +) + +// network transparent image +type image interface { + Size() (uint64, error) + Import(func(io.Reader) error, defVolume) error + String() string +} + +type localImage struct { + path string +} + +func (i *localImage) String() string { + return i.path +} + +func (i *localImage) Size() (uint64, error) { + file, err := os.Open(i.path) + if err != nil { + return 0, err + } + + fi, err := file.Stat() + if err != nil { + return 0, err + } + return uint64(fi.Size()), nil +} + +func (i *localImage) Import(copier func(io.Reader) error, vol defVolume) error { + + file, err := os.Open(i.path) + defer file.Close() + if err != nil { + return fmt.Errorf("Error while opening %s: %s", i.path, err) + } + + if fi, err := file.Stat(); err != nil { + return err + } else { + // we can skip the upload if the modification times are the same + if vol.Target.Timestamps != nil && vol.Target.Timestamps.Modification != nil { + modTime := UnixTimestamp{fi.ModTime()} + if modTime == *vol.Target.Timestamps.Modification { + log.Printf("Modification time is the same: skipping image copy") + return nil + } + } + } + + return copier(file) +} + +type httpImage struct { + url *url.URL +} + +func (i *httpImage) String() string { + return i.url.String() +} + +func (i *httpImage) Size() (uint64, error) { + response, err := http.Head(i.url.String()) + if err != nil { + return 0, err + } + length, err := strconv.Atoi(response.Header.Get("Content-Length")) + if err != nil { + return 0, err + } + return uint64(length), nil +} + +func (i *httpImage) Import(copier func(io.Reader) error, vol defVolume) error { + client := &http.Client{} + req, _ := http.NewRequest("GET", i.url.String(), nil) + + if vol.Target.Timestamps != nil && vol.Target.Timestamps.Modification != nil { + t := vol.Target.Timestamps.Modification.UTC().Format(http.TimeFormat) + req.Header.Set("If-Modified-Since", t) + } + response, err := client.Do(req) + defer response.Body.Close() + + if err != nil { + return fmt.Errorf("Error while downloading %s: %s", i.url.String(), err) + } + if response.StatusCode == http.StatusNotModified { + return nil + } + + return copier(response.Body) +} + +func newImage(source string) (image, error) { + url, err := url.Parse(source) + if err != nil { + return nil, fmt.Errorf("Can't parse source '%s' as url: %s", source, err) + } + + if strings.HasPrefix(url.Scheme, "http") { + return &httpImage{url: url}, nil + } else if url.Scheme == "file" || url.Scheme == "" { + return &localImage{path: url.Path}, nil + } else { + return nil, fmt.Errorf("Don't know how to read from '%s': %s", url.String(), err) + } +} diff --git a/libvirt/utils_volume_test.go b/libvirt/utils_volume_test.go new file mode 100644 index 00000000..5d8188fb --- /dev/null +++ b/libvirt/utils_volume_test.go @@ -0,0 +1,88 @@ +package libvirt + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "testing" +) + +func TestLocalImageDownload(t *testing.T) { + content := []byte("this is a qcow image... well, it is not") + tmpfile, err := ioutil.TempFile("", "test-image-") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpfile.Name()) + + t.Logf("Adding some content to %s", tmpfile.Name()) + if _, err := tmpfile.Write(content); err != nil { + t.Fatal(err) + } + tmpfileStat, err := tmpfile.Stat() + if err != nil { + t.Fatal(err) + } + + url := fmt.Sprintf("file://%s", tmpfile.Name()) + image, err := newImage(url) + if err != nil { + t.Errorf("Could not create local image: %v", err) + } + + t.Logf("Importing %s", url) + vol := newDefVolume() + modTime := UnixTimestamp{tmpfileStat.ModTime()} + vol.Target.Timestamps = &defTimestamps{ + Modification: &modTime, + } + + copier := func(r io.Reader) error { + t.Fatalf("ERROR: starting copy of %s... but the file is the same!", url) + return nil + } + if err = image.Import(copier, vol); err != nil { + t.Fatal("Could not copy image from %s: %v", url, err) + } + t.Log("File not copied because modification time was the same") +} + +func TestRemoteImageDownload(t *testing.T) { + content := []byte("this is a qcow image... well, it is not") + fws := fileWebServer{} + if err := fws.Start(); err != nil { + t.Fatal(err) + } + defer fws.Stop() + + url, tmpfile, err := fws.AddFile(content) + if err != nil { + t.Fatal(err) + } + + tmpfileStat, err := tmpfile.Stat() + if err != nil { + t.Fatal(err) + } + image, err := newImage(url) + if err != nil { + t.Errorf("Could not create local image: %v", err) + } + + t.Logf("Importing %s", url) + vol := newDefVolume() + modTime := UnixTimestamp{tmpfileStat.ModTime()} + vol.Target.Timestamps = &defTimestamps{ + Modification: &modTime, + } + copier := func(r io.Reader) error { + t.Fatalf("ERROR: starting copy of %s... but the file is the same!", url) + return nil + } + if err = image.Import(copier, vol); err != nil { + t.Fatal("Could not copy image from %s: %v", url, err) + } + t.Log("File not copied because modification time was the same") + +} diff --git a/libvirt/volume_def.go b/libvirt/volume_def.go index 0e72123d..281adf49 100644 --- a/libvirt/volume_def.go +++ b/libvirt/volume_def.go @@ -2,13 +2,49 @@ package libvirt import ( "encoding/xml" + "fmt" + "math" + "strconv" + "time" + + libvirt "github.com/dmacvicar/libvirt-go" ) +type UnixTimestamp struct{ time.Time } + +func (t *UnixTimestamp) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var content string + if err := d.DecodeElement(&content, &start); err != nil { + return err + } + + ts, err := strconv.ParseFloat(content, 64) + if err != nil { + return err + } + s, ns := math.Modf(ts) + *t = UnixTimestamp{time.Time(time.Unix(int64(s), int64(ns)))} + return nil +} + +func (t *UnixTimestamp) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + s := t.UTC().Unix() + ns := t.UTC().UnixNano() + return e.EncodeElement(fmt.Sprintf("%d.%d", s, ns), start) +} + +type defTimestamps struct { + Change *UnixTimestamp `xml:"ctime,omitempty"` + Modification *UnixTimestamp `xml:"mtime,omitempty"` + Access *UnixTimestamp `xml:"atime,omitempty"` +} + type defBackingStore struct { Path string `xml:"path"` Format struct { Type string `xml:"type,attr"` } `xml:"format"` + Timestamps *defTimestamps `xml:"timestamps,omitempty"` } type defVolume struct { @@ -21,11 +57,12 @@ type defVolume struct { Permissions struct { Mode int `xml:"mode,omitempty"` } `xml:"permissions,omitempty"` + Timestamps *defTimestamps `xml:"timestamps,omitempty"` } `xml:"target"` Allocation int `xml:"allocation"` Capacity struct { Unit string `xml:"unit,attr"` - Amount int64 `xml:"chardata"` + Amount uint64 `xml:"chardata"` } `xml:"capacity"` BackingStore *defBackingStore `xml:"backingStore,omitempty"` } @@ -38,3 +75,29 @@ func newDefVolume() defVolume { volumeDef.Capacity.Amount = 1 return volumeDef } + +// Creates a volume definition from a XML +func newDefVolumeFromXML(s string) (defVolume, error) { + var volumeDef defVolume + err := xml.Unmarshal([]byte(s), &volumeDef) + if err != nil { + return defVolume{}, err + } + return volumeDef, nil +} + +func newDefVolumeFromLibvirt(volume *libvirt.VirStorageVol) (defVolume, error) { + name, err := volume.GetName() + if err != nil { + return defVolume{}, fmt.Errorf("could not get name for volume: %s.", err) + } + volumeDefXml, err := volume.GetXMLDesc(0) + if err != nil { + return defVolume{}, fmt.Errorf("could not get XML description for volume %s: %s.", name, err) + } + volumeDef, err := newDefVolumeFromXML(volumeDefXml) + if err != nil { + return defVolume{}, fmt.Errorf("could not get a volume definition from XML for %s: %s.", volumeDef.Name, err) + } + return volumeDef, nil +} diff --git a/libvirt/volume_def_test.go b/libvirt/volume_def_test.go index 6936154c..b159a2dd 100644 --- a/libvirt/volume_def_test.go +++ b/libvirt/volume_def_test.go @@ -12,6 +12,54 @@ func init() { spew.Config.Indent = "\t" } + +func TestVolumeUnmarshal(t *testing.T) { + xmlDesc := ` + + caasp_master.img + /home/user/.libvirt/images/image.img + + + 42949672960 + 900800512 + + /home/user/.libvirt/images/image.img + + + 0644 + 480 + 473 + + + 1488789260.012293492 + 1488802938.454893390 + 1488802938.454893390 + + + + /home/user/.libvirt/images/image.img + + + 0644 + 480 + 473 + + + 1488541864.606322102 + 1488541858.638308597 + 1488541864.526321921 + + + + ` + + def, err := newDefVolumeFromXML(xmlDesc) + if err != nil { + t.Fatalf("could not unmarshall volume definition:\n%s", err) + } + t.Logf("Unmarshalled volume:\n%s", spew.Sdump(def)) +} + func TestDefaultVolumeMarshall(t *testing.T) { b := newDefVolume() prettyB := spew.Sdump(b) -- cgit v1.2.3