summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libvirt/cloudinit_def.go22
-rw-r--r--libvirt/resource_libvirt_volume.go185
-rw-r--r--libvirt/resource_libvirt_volume_test.go92
-rw-r--r--libvirt/utils_net.go65
-rw-r--r--libvirt/utils_volume.go120
-rw-r--r--libvirt/utils_volume_test.go88
-rw-r--r--libvirt/volume_def.go65
-rw-r--r--libvirt/volume_def_test.go48
8 files changed, 523 insertions, 162 deletions
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 := `
+ <volume type='file'>
+ <name>caasp_master.img</name>
+ <key>/home/user/.libvirt/images/image.img</key>
+ <source>
+ </source>
+ <capacity unit='bytes'>42949672960</capacity>
+ <allocation unit='bytes'>900800512</allocation>
+ <target>
+ <path>/home/user/.libvirt/images/image.img</path>
+ <format type='qcow2'/>
+ <permissions>
+ <mode>0644</mode>
+ <owner>480</owner>
+ <group>473</group>
+ </permissions>
+ <timestamps>
+ <atime>1488789260.012293492</atime>
+ <mtime>1488802938.454893390</mtime>
+ <ctime>1488802938.454893390</ctime>
+ </timestamps>
+ </target>
+ <backingStore>
+ <path>/home/user/.libvirt/images/image.img</path>
+ <format type='qcow2'/>
+ <permissions>
+ <mode>0644</mode>
+ <owner>480</owner>
+ <group>473</group>
+ </permissions>
+ <timestamps>
+ <atime>1488541864.606322102</atime>
+ <mtime>1488541858.638308597</mtime>
+ <ctime>1488541864.526321921</ctime>
+ </timestamps>
+ </backingStore>
+ </volume>
+ `
+
+ 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)