From 58bfa4b78a143699dff9962b1781a66b202e718e Mon Sep 17 00:00:00 2001 From: Duncan Mac-Vicar P Date: Thu, 16 Nov 2017 13:44:15 +0100 Subject: Add support for remote http disks (qemu http curl backend) --- libvirt/resource_libvirt_domain.go | 111 +++++++++++++++++++++++--------- libvirt/resource_libvirt_domain_test.go | 68 +++++++++++++++++++ 2 files changed, 148 insertions(+), 31 deletions(-) diff --git a/libvirt/resource_libvirt_domain.go b/libvirt/resource_libvirt_domain.go index 317bfd33..90a0896d 100644 --- a/libvirt/resource_libvirt_domain.go +++ b/libvirt/resource_libvirt_domain.go @@ -7,6 +7,7 @@ import ( "fmt" "log" "net" + "net/url" "os" "strconv" "strings" @@ -382,7 +383,6 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error diskKey := fmt.Sprintf("disk.%d", i) diskMap := d.Get(diskKey).(map[string]interface{}) - volumeKey := diskMap["volume_id"].(string) if _, ok := diskMap["scsi"].(string); ok { disk.Target.Bus = "scsi" scsiDisk = true @@ -392,17 +392,47 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error disk.WWN = randomWWN(10) } } - diskVolume, err := virConn.LookupStorageVolByKey(volumeKey) - if err != nil { - return fmt.Errorf("Can't retrieve volume %s", volumeKey) - } - diskVolumeFile, err := diskVolume.GetPath() - if err != nil { - return fmt.Errorf("Error retrieving volume file: %s", err) - } - disk.Source = &libvirtxml.DomainDiskSource{ - File: diskVolumeFile, + if _, ok := diskMap["volume_id"].(string); ok { + volumeKey := diskMap["volume_id"].(string) + + diskVolume, err := virConn.LookupStorageVolByKey(volumeKey) + if err != nil { + return fmt.Errorf("Can't retrieve volume %s", volumeKey) + } + diskVolumeFile, err := diskVolume.GetPath() + if err != nil { + return fmt.Errorf("Error retrieving volume file: %s", err) + } + + disk.Source = &libvirtxml.DomainDiskSource{ + File: diskVolumeFile, + } + } else if _, ok := diskMap["url"].(string); ok { + // Support for remote, read-only http disks + // useful for booting CDs + disk.Type = "network" + url, err := url.Parse(diskMap["url"].(string)) + if err != nil { + return err + } + + disk.Source = &libvirtxml.DomainDiskSource{ + Protocol: url.Scheme, + Name: url.Path, + Hosts: []libvirtxml.DomainDiskSourceHost{ + libvirtxml.DomainDiskSourceHost{ + Name: url.Hostname(), + Port: url.Port(), + }, + }, + } + if strings.HasSuffix(url.Path, ".iso") { + disk.Device = "cdrom" + } + if !strings.HasSuffix(url.Path, ".qcow2") { + disk.Driver.Type = "raw" + } } disks = append(disks, disk) @@ -870,33 +900,52 @@ func resourceLibvirtDomainRead(d *schema.ResourceData, meta interface{}) error { disks := make([]map[string]interface{}, 0) for _, diskDef := range domainDef.Devices.Disks { - var virVol *libvirt.StorageVol - if len(diskDef.Source.File) > 0 { - virVol, err = virConn.LookupStorageVolByPath(diskDef.Source.File) - } else { - virPool, err := virConn.LookupStoragePoolByName(diskDef.Source.Pool) + // network drives do not have a volume associated + if diskDef.Type == "network" { + if len(diskDef.Source.Hosts) < 1 { + return fmt.Errorf("Network disk does not contain any hosts") + } + url, err := url.Parse(fmt.Sprintf("%s://%s:%s%s", + diskDef.Source.Protocol, + diskDef.Source.Hosts[0].Name, + diskDef.Source.Hosts[0].Port, + diskDef.Source.Name)) if err != nil { - return fmt.Errorf("Error retrieving pool for disk: %s", err) + return err + } + disk := map[string]interface{}{ + "url": url.String(), } - defer virPool.Free() + disks = append(disks, disk) + } else { + var virVol *libvirt.StorageVol + if len(diskDef.Source.File) > 0 { + virVol, err = virConn.LookupStorageVolByPath(diskDef.Source.File) + } else { + virPool, err := virConn.LookupStoragePoolByName(diskDef.Source.Pool) + if err != nil { + return fmt.Errorf("Error retrieving pool for disk: %s", err) + } + defer virPool.Free() - virVol, err = virPool.LookupStorageVolByName(diskDef.Source.Volume) - } + virVol, err = virPool.LookupStorageVolByName(diskDef.Source.Volume) + } - if err != nil { - return fmt.Errorf("Error retrieving volume for disk: %s", err) - } - defer virVol.Free() + if err != nil { + return fmt.Errorf("Error retrieving volume for disk: %s", err) + } + defer virVol.Free() - virVolKey, err := virVol.GetKey() - if err != nil { - return fmt.Errorf("Error retrieving volume for disk: %s", err) - } + virVolKey, err := virVol.GetKey() + if err != nil { + return fmt.Errorf("Error retrieving volume for disk: %s", err) + } - disk := map[string]interface{}{ - "volume_id": virVolKey, + disk := map[string]interface{}{ + "volume_id": virVolKey, + } + disks = append(disks, disk) } - disks = append(disks, disk) } d.Set("disks", disks) diff --git a/libvirt/resource_libvirt_domain_test.go b/libvirt/resource_libvirt_domain_test.go index 954f1ba5..0fe6d078 100644 --- a/libvirt/resource_libvirt_domain_test.go +++ b/libvirt/resource_libvirt_domain_test.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "log" + "net/url" "os" "testing" @@ -202,6 +203,38 @@ func TestAccLibvirtDomain_ScsiDisk(t *testing.T) { } +func TestAccLibvirtDomainUrlDisk(t *testing.T) { + var domain libvirt.Domain + u, err := url.Parse("http://download.opensuse.org/tumbleweed/iso/openSUSE-Tumbleweed-DVD-x86_64-Current.iso") + if err != nil { + t.Error(err) + } + + var configUrl = fmt.Sprintf(` + resource "libvirt_domain" "acceptance-test-domain" { + name = "terraform-test-domain" + disk { + url = "%s" + } + }`, u.String()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibvirtDomainDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: configUrl, + Check: resource.ComposeTestCheckFunc( + testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain), + testAccCheckLibvirtUrlDisk(u, &domain), + ), + }, + }, + }) + +} + func TestAccLibvirtDomain_NetworkInterface(t *testing.T) { var domain libvirt.Domain @@ -590,6 +623,41 @@ func testAccCheckLibvirtScsiDisk(n string, domain *libvirt.Domain) resource.Test } } +func testAccCheckLibvirtUrlDisk(u *url.URL, domain *libvirt.Domain) resource.TestCheckFunc { + return func(s *terraform.State) error { + xmlDesc, err := domain.GetXMLDesc(0) + if err != nil { + return fmt.Errorf("Error retrieving libvirt domain XML description: %s", err) + } + + domainDef := newDomainDef() + err = xml.Unmarshal([]byte(xmlDesc), &domainDef) + if err != nil { + return fmt.Errorf("Error reading libvirt domain XML description: %s", err) + } + + disks := domainDef.Devices.Disks + for _, disk := range disks { + if disk.Type != "network" { + return fmt.Errorf("Disk type is not network") + } + if disk.Source.Protocol != u.Scheme { + return fmt.Errorf("Disk protocol is not %s", u.Scheme) + } + if disk.Source.Name != u.Path { + return fmt.Errorf("Disk name is not %s", u.Path) + } + if len(disk.Source.Hosts) < 1 { + return fmt.Errorf("Disk has no hosts defined") + } + if disk.Source.Hosts[0].Name != u.Hostname() { + return fmt.Errorf("Disk hostname is not %s", u.Hostname()) + } + } + return nil + } +} + func createNvramFile() (string, error) { // size of an accepted, valid, nvram backing store NVRAMDummyBuffer := make([]byte, 131072) -- cgit v1.2.3 From bb8be0df22f5635a6d2efc70d4372d2321dfc0f2 Mon Sep 17 00:00:00 2001 From: Duncan Mac-Vicar P Date: Thu, 16 Nov 2017 13:45:56 +0100 Subject: Reuse newDefDisk() --- libvirt/disk_def.go | 2 ++ libvirt/resource_libvirt_domain.go | 13 +------------ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/libvirt/disk_def.go b/libvirt/disk_def.go index 3283f03b..785542b6 100644 --- a/libvirt/disk_def.go +++ b/libvirt/disk_def.go @@ -1,6 +1,7 @@ package libvirt import ( + "fmt" "math/rand" "github.com/libvirt/libvirt-go-xml" @@ -14,6 +15,7 @@ func newDefDisk() libvirtxml.DomainDisk { Device: "disk", Target: &libvirtxml.DomainDiskTarget{ Bus: "virtio", + Dev: fmt.Sprintf("vd%s", DiskLetterForIndex(i)), }, Driver: &libvirtxml.DomainDiskDriver{ Name: "qemu", diff --git a/libvirt/resource_libvirt_domain.go b/libvirt/resource_libvirt_domain.go index 90a0896d..dfb9d2f9 100644 --- a/libvirt/resource_libvirt_domain.go +++ b/libvirt/resource_libvirt_domain.go @@ -368,18 +368,7 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error var disks []libvirtxml.DomainDisk var scsiDisk = false for i := 0; i < disksCount; i++ { - disk := libvirtxml.DomainDisk{ - Type: "file", - Device: "disk", - Target: &libvirtxml.DomainDiskTarget{ - Bus: "virtio", - Dev: fmt.Sprintf("vd%s", DiskLetterForIndex(i)), - }, - Driver: &libvirtxml.DomainDiskDriver{ - Name: "qemu", - Type: "qcow2", - }, - } + disk := newDefDisk() diskKey := fmt.Sprintf("disk.%d", i) diskMap := d.Get(diskKey).(map[string]interface{}) -- cgit v1.2.3 From 5ea094a4686747eba27dd1ced6b380300424f88f Mon Sep 17 00:00:00 2001 From: Duncan Mac-Vicar P Date: Thu, 16 Nov 2017 13:50:06 +0100 Subject: Document disks from url --- libvirt/disk_def.go | 2 +- libvirt/disk_def_test.go | 2 +- libvirt/resource_libvirt_domain.go | 2 +- website/docs/r/domain.html.markdown | 11 ++++++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/libvirt/disk_def.go b/libvirt/disk_def.go index 785542b6..f4e4e86a 100644 --- a/libvirt/disk_def.go +++ b/libvirt/disk_def.go @@ -9,7 +9,7 @@ import ( const oui = "05abcd" -func newDefDisk() libvirtxml.DomainDisk { +func newDefDisk(i int) libvirtxml.DomainDisk { return libvirtxml.DomainDisk{ Type: "file", Device: "disk", diff --git a/libvirt/disk_def_test.go b/libvirt/disk_def_test.go index 1e3141d5..a6184cc6 100644 --- a/libvirt/disk_def_test.go +++ b/libvirt/disk_def_test.go @@ -13,7 +13,7 @@ func init() { } func TestDefaultDiskMarshall(t *testing.T) { - b := newDefDisk() + b := newDefDisk(0) buf := new(bytes.Buffer) enc := xml.NewEncoder(buf) enc.Indent(" ", " ") diff --git a/libvirt/resource_libvirt_domain.go b/libvirt/resource_libvirt_domain.go index dfb9d2f9..e5034352 100644 --- a/libvirt/resource_libvirt_domain.go +++ b/libvirt/resource_libvirt_domain.go @@ -368,7 +368,7 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error var disks []libvirtxml.DomainDisk var scsiDisk = false for i := 0; i < disksCount; i++ { - disk := newDefDisk() + disk := newDefDisk(i) diskKey := fmt.Sprintf("disk.%d", i) diskMap := d.Get(diskKey).(map[string]interface{}) diff --git a/website/docs/r/domain.html.markdown b/website/docs/r/domain.html.markdown index c9bbfbaa..b047a6e7 100644 --- a/website/docs/r/domain.html.markdown +++ b/website/docs/r/domain.html.markdown @@ -143,7 +143,12 @@ resource "libvirt_domain" "my_machine" { The `disk` block supports: -* `volume_id` - (Required) The volume id to use for this disk. +* `volume_id` - (Optional) The volume id to use for this disk. +* `url` - (Optional) The http url to use as the block device for this disk (read-only) + +While both `volume_id` and `url` are optional, it is intended that you use either a volume or a +url. + * `scsi` - (Optional) Use a scsi controller for this disk. The controller model is set to `virtio-scsi` * `wwn` - (Optional) Specify a WWN to use for the disk if the disk is using @@ -167,6 +172,10 @@ resource "libvirt_domain" "domain1" { volume_id = "${libvirt_volume.mydisk.id}" scsi = "yes" } + + disk { + url = "http://foo.com/install.iso" + } } ``` -- cgit v1.2.3 From 49dadde97886f31fd32b220e1918c5025c6c317e Mon Sep 17 00:00:00 2001 From: Duncan Mac-Vicar P Date: Sun, 19 Nov 2017 17:01:50 +0100 Subject: golint: Url -> URL --- libvirt/resource_libvirt_domain_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libvirt/resource_libvirt_domain_test.go b/libvirt/resource_libvirt_domain_test.go index 0fe6d078..65d9f898 100644 --- a/libvirt/resource_libvirt_domain_test.go +++ b/libvirt/resource_libvirt_domain_test.go @@ -203,14 +203,14 @@ func TestAccLibvirtDomain_ScsiDisk(t *testing.T) { } -func TestAccLibvirtDomainUrlDisk(t *testing.T) { +func TestAccLibvirtDomainURLDisk(t *testing.T) { var domain libvirt.Domain u, err := url.Parse("http://download.opensuse.org/tumbleweed/iso/openSUSE-Tumbleweed-DVD-x86_64-Current.iso") if err != nil { t.Error(err) } - var configUrl = fmt.Sprintf(` + var configURL = fmt.Sprintf(` resource "libvirt_domain" "acceptance-test-domain" { name = "terraform-test-domain" disk { @@ -224,10 +224,10 @@ func TestAccLibvirtDomainUrlDisk(t *testing.T) { CheckDestroy: testAccCheckLibvirtDomainDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: configUrl, + Config: configURL, Check: resource.ComposeTestCheckFunc( testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain), - testAccCheckLibvirtUrlDisk(u, &domain), + testAccCheckLibvirtURLDisk(u, &domain), ), }, }, @@ -623,7 +623,7 @@ func testAccCheckLibvirtScsiDisk(n string, domain *libvirt.Domain) resource.Test } } -func testAccCheckLibvirtUrlDisk(u *url.URL, domain *libvirt.Domain) resource.TestCheckFunc { +func testAccCheckLibvirtURLDisk(u *url.URL, domain *libvirt.Domain) resource.TestCheckFunc { return func(s *terraform.State) error { xmlDesc, err := domain.GetXMLDesc(0) if err != nil { -- cgit v1.2.3