From b56a61c776f01ddfc07040fade0f170a4a578af9 Mon Sep 17 00:00:00 2001 From: Boris <91973490+jimnydev@users.noreply.github.com> Date: Thu, 3 Oct 2024 00:53:51 +0200 Subject: [PATCH] feat: lvm storage pool impl (#1088) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: lvm storage pool impl This feature also adds support for target and source blocks in pool. Deprecating the old top-level path, and opening the door for supporting more pool backends, following libvirt description closer. - refactor the lvm pool implementation - add proper source and target so we can add more pool implementations later - handle deprecation of top level path properly - less checks, lets libvirt handle it - add acceptance tests - refactor the test helper block device creation out of domain - add docs - skip the tests if the host does not support logical pools - skip early if acceptance tests are disabled * fix: dir path is actually on target, not source * add name attribute to pool source * cleanup support for existing vg * move code to configure provider to common function --------- Co-authored-by: dpap Co-authored-by: Duncan Mac-Vicar P. Co-authored-by: Boris Momčilović --- go.mod | 10 +- go.sum | 21 +++ libvirt/helper/test/temp_loop_dev.go | 178 ++++++++++++++++++++ libvirt/pool.go | 6 +- libvirt/pool_def.go | 31 ++++ libvirt/resource_libvirt_domain_test.go | 77 ++------- libvirt/resource_libvirt_pool.go | 208 +++++++++++++++++++----- libvirt/resource_libvirt_pool_test.go | 169 ++++++++++++++++++- libvirt/resource_libvirt_volume_test.go | 8 +- website/docs/r/pool.html.markdown | 9 +- 10 files changed, 590 insertions(+), 127 deletions(-) create mode 100644 libvirt/helper/test/temp_loop_dev.go create mode 100644 libvirt/pool_def.go diff --git a/go.mod b/go.mod index d8c4e3016..012664f6e 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,9 @@ require ( github.com/coreos/go-systemd/v22 v22.0.0 // indirect github.com/coreos/ignition/v2 v2.14.0 // indirect github.com/coreos/vcontext v0.0.0-20211021162308-f1dbbca7bef4 // indirect + github.com/diskfs/go-diskfs v1.4.2 // indirect + github.com/djherbis/times v1.6.0 // indirect + github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab // indirect github.com/fatih/color v1.17.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -52,6 +55,7 @@ require ( github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -60,8 +64,12 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.1.0 // indirect + github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/xattr v0.4.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect + github.com/ulikunitz/xz v0.5.11 // indirect github.com/vincent-petithory/dataurl v1.0.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect @@ -88,7 +96,7 @@ replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110 replace golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce => github.com/dmacvicar/golang-x-crypto v0.0.0-20220126233154-a96af8f07497 -go 1.22.0 +go 1.23 toolchain go1.23.1 diff --git a/go.sum b/go.sum index 5ecf9418b..885619c82 100644 --- a/go.sum +++ b/go.sum @@ -109,8 +109,14 @@ github.com/digitalocean/go-libvirt v0.0.0-20221205150000-2939327a8519 h1:OpkN/n4 github.com/digitalocean/go-libvirt v0.0.0-20221205150000-2939327a8519/go.mod h1:WyJJyfmJ0gWJvjV+ZH4DOgtOYZc1KOvYyBXWCLKxsUU= github.com/digitalocean/go-libvirt v0.0.0-20240916165608-bff44a349d9d h1:3lleylIw0Dvrlff8+3c+xp0MboNcRz9tMinZNeiGFpc= github.com/digitalocean/go-libvirt v0.0.0-20240916165608-bff44a349d9d/go.mod h1:+tYha+y/luhAZImNm1TtQjBE2NWEtP5HDLyE5XG+RNA= +github.com/diskfs/go-diskfs v1.4.2 h1:khBr9RTkqAZFaMYK7PP8NooL30hqj3bSgRmj3Ouguls= +github.com/diskfs/go-diskfs v1.4.2/go.mod h1:ss1uAUBhgDdEOewZFDWWpYqJFjNPbK7hYSjRoQE+D94= +github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= +github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= github.com/dmacvicar/terraform-provider-ignition/v2 v2.1.3-0.20210701165004-13acf61ca184 h1:z9nAMxjTxJXAOmGWMvW5SS0xUwCEJNVjfTesp+07ynE= github.com/dmacvicar/terraform-provider-ignition/v2 v2.1.3-0.20210701165004-13acf61ca184/go.mod h1:pToe90z63vSP7LIYtZ85Q+g88WvRmdNsg73YZYJZg64= +github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY= +github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -144,6 +150,7 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -345,6 +352,8 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= @@ -412,11 +421,16 @@ github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= +github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pin/tftp v2.1.0+incompatible/go.mod h1:xVpZOMCXTy+A5QMjEVN0Glwa1sUvaJhFXbr/aAxuxGY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= +github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -427,6 +441,8 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0= +github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -450,6 +466,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vincent-petithory/dataurl v0.0.0-20160330182126-9a301d65acbb/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI= @@ -650,8 +668,11 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/libvirt/helper/test/temp_loop_dev.go b/libvirt/helper/test/temp_loop_dev.go new file mode 100644 index 000000000..ebe4193e2 --- /dev/null +++ b/libvirt/helper/test/temp_loop_dev.go @@ -0,0 +1,178 @@ +package test + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +type TempBlockDevice struct { + TempFile string + LoopDevice string +} + +// returns the temporary file, the device path and the error. +func CreateTempFormattedLoopDevice(t *testing.T, name string) (*TempBlockDevice, error) { + blockDev, err := CreateTempLoopDevice(t, name) + if err != nil { + return nil, err + } + + //nolint:gosec + cmd := exec.Command("/sbin/mkfs.ext4", "-F", "-q", blockDev.LoopDevice) + log.Printf("[DEBUG] executing command: %s", strings.Join(cmd.Args, " ")) + if err := cmd.Run(); err != nil { + if err := cleanupLoop(blockDev.LoopDevice); err != nil { + return nil, err + } + + if err := cleanupFile(blockDev.TempFile); err != nil { + return nil, err + } + + return nil, fmt.Errorf("error formatting file system: %w", err) + } + + return blockDev, nil +} + +// returns the temporary file, the device and the error. +func CreateTempLVMGroupDevice(t *testing.T, name string) (*TempBlockDevice, error) { + blockDev, err := CreateTempLoopDevice(t, name) + if err != nil { + return nil, err + } + + //nolint:gosec + cmd := exec.Command("sudo", "pvcreate", blockDev.LoopDevice) + log.Printf("[DEBUG] executing command: %s", strings.Join(cmd.Args, " ")) + if err := cmd.Run(); err != nil { + if err := cleanupLoop(blockDev.LoopDevice); err != nil { + return nil, err + } + + if err := cleanupFile(blockDev.TempFile); err != nil { + return nil, err + } + + return nil, fmt.Errorf("error creating LVM partition on %s: %w", blockDev.LoopDevice, err) + } + + //nolint:gosec + cmd = exec.Command("sudo", "vgcreate", name, blockDev.LoopDevice) + log.Printf("[DEBUG] executing command: %s", strings.Join(cmd.Args, " ")) + if err := cmd.Run(); err != nil { + if err := cleanupLoop(blockDev.LoopDevice); err != nil { + return nil, err + } + + if err := cleanupFile(blockDev.TempFile); err != nil { + return nil, err + } + + return nil, fmt.Errorf("error creating LVM partition on %s: %w", blockDev.LoopDevice, err) + } + + return blockDev, nil +} + +// returns the temporary file, the device and the error. +func CreateTempLoopDevice(t *testing.T, name string) (*TempBlockDevice, error) { + log.Print("[DEBUG] creating a temporary file for loop device") + + // Create a 1MB temp file + filename := filepath.Join(t.TempDir(), name) + + //nolint:gosec + cmd := exec.Command("dd", "if=/dev/zero", fmt.Sprintf("of=%s", filename), "bs=1024", "count=2048") + log.Printf("[DEBUG] executing command: %s\n", strings.Join(cmd.Args, " ")) + if err := cmd.Run(); err != nil { + if err := cleanupFile(filename); err != nil { + return nil, err + } + + return nil, fmt.Errorf("Error creating file %s: %w", filename, err) + } + + // Find an available loop device. + cmd = exec.Command("sudo", "/sbin/losetup", "--find") + loopdevStr, err := cmd.Output() + log.Printf("[DEBUG] executing command: %s", strings.Join(cmd.Args, " ")) + if err != nil { + if err := cleanupFile(filename); err != nil { + return nil, err + } + + return nil, fmt.Errorf("Error searching for available loop device: %w", err) + } + loopdev := filepath.Clean(strings.TrimRight(string(loopdevStr), "\n")) + + // give the same permissions to the loop device as the backing file. + cmd = exec.Command("sudo", "chown", "--reference", filename, loopdev) + log.Printf("[DEBUG] executing command: %s", strings.Join(cmd.Args, " ")) + if err := cmd.Run(); err != nil { + if err := cleanupFile(filename); err != nil { + return nil, err + } + + return nil, fmt.Errorf("Error copying permissions from %s: %w", filename, err) + } + + // attach the file to a loop device. + cmd = exec.Command("sudo", "/sbin/losetup", loopdev, filename) + log.Printf("[DEBUG] executing command: %s", strings.Join(cmd.Args, " ")) + if err := cmd.Run(); err != nil { + if err := cleanupFile(filename); err != nil { + return nil, err + } + + return nil, fmt.Errorf("Error setting up loop device: %w", err) + } + + log.Printf("[DEBUG] temporary file %s attached to loop device %s", filename, loopdev) + + return &TempBlockDevice{TempFile: filename, LoopDevice: loopdev}, nil +} + +func (b *TempBlockDevice) Cleanup() error { + err := cleanupLoop(b.LoopDevice) + if err != nil { + return err + } + + err = cleanupFile(b.TempFile) + if err != nil { + return err + } + + return nil +} + +func cleanupLoop(loopDevice string) error { + cmd := exec.Command("sudo", "losetup", "-d", loopDevice) + if err := cmd.Run(); err != nil { + log.Printf("[DEBUG] error detaching loop device %s: %s", loopDevice, err) + return err + } + log.Printf("[DEBUG] detaching loop device %s", loopDevice) + + return nil +} + +func cleanupFile(tempFile string) error { + if err := os.Remove(tempFile); err != nil { + log.Printf("[DEBUG] error removing temporary file %s: %s", tempFile, err) + return err + } + log.Printf("[DEBUG] removing temporary file %s", tempFile) + + return nil +} + +func (b *TempBlockDevice) String() string { + return fmt.Sprintf("TempFile: %s, LoopDevice: %s", b.TempFile, b.LoopDevice) +} diff --git a/libvirt/pool.go b/libvirt/pool.go index c1050f29f..95f660bf9 100644 --- a/libvirt/pool.go +++ b/libvirt/pool.go @@ -18,7 +18,7 @@ func poolExistsStateRefreshFunc(virConn *libvirt.Libvirt, uuid libvirt.UUID) ret _, err := virConn.StoragePoolLookupByUUID(uuid) if err != nil { if isError(err, libvirt.ErrNoStoragePool) { - log.Printf("pool %s does not exist", uuid) + log.Printf("pool %v does not exist", uuidString(uuid)) return virConn, poolStateConfNotExists, nil } return virConn, poolStateConfNotExists, err @@ -28,7 +28,7 @@ func poolExistsStateRefreshFunc(virConn *libvirt.Libvirt, uuid libvirt.UUID) ret } func waitForStatePoolExists(ctx context.Context, virConn *libvirt.Libvirt, uuid libvirt.UUID) error { - log.Printf("Waiting for pool %s to appear...", uuid) + log.Printf("Waiting for pool %v to appear...", uuidString(uuid)) stateConf := &retry.StateChangeConf{ Pending: []string{poolStateConfNotExists}, Target: []string{poolStateConfExists}, @@ -46,7 +46,7 @@ func waitForStatePoolExists(ctx context.Context, virConn *libvirt.Libvirt, uuid // waitForStatePoolDeleted waits for a storage pool to be removed. func waitForStatePoolDeleted(ctx context.Context, virConn *libvirt.Libvirt, uuid libvirt.UUID) error { - log.Printf("waiting for pool %s to be deleted...", uuid) + log.Printf("waiting for pool %v to be deleted...", uuidString(uuid)) stateConf := &retry.StateChangeConf{ Pending: []string{poolStateConfExists}, Target: []string{poolStateConfNotExists}, diff --git a/libvirt/pool_def.go b/libvirt/pool_def.go new file mode 100644 index 000000000..d65f59e95 --- /dev/null +++ b/libvirt/pool_def.go @@ -0,0 +1,31 @@ +package libvirt + +import ( + "encoding/xml" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + + "github.com/digitalocean/go-libvirt" + "libvirt.org/go/libvirtxml" +) + +func newDefPoolFromLibvirt(virConn *libvirt.Libvirt, pool libvirt.StoragePool) (libvirtxml.StoragePool, diag.Diagnostics) { + poolDefXML, err := virConn.StoragePoolGetXMLDesc(pool, 0) + if err != nil { + return libvirtxml.StoragePool{}, diag.Errorf("could not get XML description for pool %s: %s", pool.Name, err) + } + poolDef, err := newDefPoolFromXML(poolDefXML) + if err != nil { + return libvirtxml.StoragePool{}, diag.Errorf("could not get a pool definition from XML for %s: %s", pool.Name, err) + } + return poolDef, nil +} + +func newDefPoolFromXML(s string) (libvirtxml.StoragePool, error) { + var poolDef libvirtxml.StoragePool + err := xml.Unmarshal([]byte(s), &poolDef) + if err != nil { + return libvirtxml.StoragePool{}, err + } + return poolDef, nil +} diff --git a/libvirt/resource_libvirt_domain_test.go b/libvirt/resource_libvirt_domain_test.go index 66b914ac8..8127f2d67 100644 --- a/libvirt/resource_libvirt_domain_test.go +++ b/libvirt/resource_libvirt_domain_test.go @@ -5,11 +5,11 @@ import ( "log" "net/url" "os" - "os/exec" "path/filepath" - "strings" "testing" + testhelper "github.com/dmacvicar/terraform-provider-libvirt/libvirt/helper/test" + libvirt "github.com/digitalocean/go-libvirt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -343,24 +343,17 @@ func TestAccLibvirtDomain_BlockDevice(t *testing.T) { randomDomainName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) randomDeviceName := acctest.RandStringFromCharSet(33, acctest.CharSetAlpha) - tmpfile, loopdev, err := createTempBlockDev(randomDeviceName) - defer func() { - if err := os.Remove(tmpfile); err != nil { - log.Printf("Error removing temporary file %s: %s\n", tmpfile, err) - } - }() + blockDev, err := testhelper.CreateTempFormattedLoopDevice(t, randomDeviceName) + if err != nil { + t.Fatal(err) + } defer func() { - cmd := exec.Command("sudo", "/sbin/losetup", "--detach", loopdev) - if err := cmd.Run(); err != nil { - log.Printf("Error detaching loop device %s: %s\n", loopdev, err) + if err := blockDev.Cleanup(); err != nil { + t.Errorf("Error cleaning up loop device %s: %s", blockDev, err) } }() - if err != nil { - t.Fatal(err) - } - configBlockDevice := fmt.Sprintf(` resource "libvirt_domain" "%s" { @@ -370,7 +363,7 @@ func TestAccLibvirtDomain_BlockDevice(t *testing.T) { block_device = "%s" } - }`, randomDomainName, randomDomainName, loopdev) + }`, randomDomainName, randomDomainName, blockDev.LoopDevice) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -381,7 +374,7 @@ func TestAccLibvirtDomain_BlockDevice(t *testing.T) { Config: configBlockDevice, Check: resource.ComposeTestCheckFunc( testAccCheckLibvirtDomainExists("libvirt_domain."+randomDomainName, &domain), - testAccCheckLibvirtBlockDevice(loopdev, &domain), + testAccCheckLibvirtBlockDevice(blockDev.LoopDevice, &domain), ), }, }, @@ -1179,56 +1172,6 @@ func testAccCheckLibvirtDomainKernelInitrdCmdline(domain *libvirt.Domain, kernel } } -// Creates a temporary file and attaches it to an available loop device -// Returns a the full path to the temporary file and the associated loop device. -func createTempBlockDev(devname string) (string, string, error) { - fmt.Printf("Creating a temporary file for loop device\n") - - // Create a 1MB temp file - filename := filepath.Join(os.TempDir(), devname) - - //nolint:gosec // G204 not sure why gosec complains but we should replace dd call - cmd := exec.Command("dd", "if=/dev/zero", "of="+filename, "bs=1024", "count=1024") - fmt.Printf("Executing command: %s\n", strings.Join(cmd.Args, " ")) - if err := cmd.Run(); err != nil { - return "", "", fmt.Errorf("Error creating file %s: %w", filename, err) - } - - // Format the file - cmd = exec.Command("/sbin/mkfs.ext4", "-F", "-q", filename) - fmt.Printf("Executing command: %s\n", strings.Join(cmd.Args, " ")) - if err := cmd.Run(); err != nil { - return "", "", fmt.Errorf("Error formatting file system: %w", err) - } - - // Find an available loop device - cmd = exec.Command("sudo", "/sbin/losetup", "--find") - loopdevStr, err := cmd.Output() - fmt.Printf("Executing command: %s\n", strings.Join(cmd.Args, " ")) - if err != nil { - return "", "", fmt.Errorf("Error searching for available loop device: %w", err) - } - loopdev := strings.TrimRight(string(loopdevStr), "\n") - - // give the same permissions to the loop device as the backing file - cmd = exec.Command("sudo", "chown", "--reference", filename, loopdev) - fmt.Printf("Executing command: %s\n", strings.Join(cmd.Args, " ")) - if err := cmd.Run(); err != nil { - return "", "", fmt.Errorf("Error copying permissions from %s: %w", filename, err) - } - - // Mount the file to a loop device - cmd = exec.Command("sudo", "/sbin/losetup", loopdev, filename) - fmt.Printf("Executing command: %s\n", strings.Join(cmd.Args, " ")) - if err := cmd.Run(); err != nil { - return "", "", fmt.Errorf("Error setting up loop device: %w", err) - } - - log.Printf("Temporary file %s attached to loop device %s", filename, loopdev) - - return filename, strings.TrimRight(string(loopdev), "\n"), nil -} - func createNvramFile(_ *testing.T) (string, error) { // size of an accepted, valid, nvram backing store nvramDummyBuffer := make([]byte, 131072) diff --git a/libvirt/resource_libvirt_pool.go b/libvirt/resource_libvirt_pool.go index 3154ffd8c..f26c434d2 100644 --- a/libvirt/resource_libvirt_pool.go +++ b/libvirt/resource_libvirt_pool.go @@ -3,6 +3,7 @@ package libvirt import ( "context" "encoding/xml" + "fmt" "log" libvirt "github.com/digitalocean/go-libvirt" @@ -11,6 +12,22 @@ import ( "libvirt.org/go/libvirtxml" ) +func resourceLibvirtPoolCustomizeDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { + // target path is computed for logical + if target := diff.GetRawConfig().GetAttr("target"); target.IsKnown() && len(target.AsValueSlice()) > 0 { + if path := target.AsValueSlice()[0].GetAttr("path"); path.IsKnown() { + oldTargetPath, newTargetPath := diff.GetChange("target.0.path") + if oldTargetPath != newTargetPath { + if err := diff.ForceNew("target.0.path"); err != nil { + return err + } + } + } + } + + return nil +} + func resourceLibvirtPool() *schema.Resource { return &schema.Resource{ CreateContext: resourceLibvirtPoolCreate, @@ -45,6 +62,51 @@ func resourceLibvirtPool() *schema.Resource { Optional: true, ForceNew: true, }, + "source": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "device": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "path": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + }, + }, + }, + "target": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "path": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"path"}, + }, + }, + }, + }, "xml": { Type: schema.TypeList, Optional: true, @@ -60,17 +122,19 @@ func resourceLibvirtPool() *schema.Resource { }, }, }, - - // Dir-specific attributes + // deprecated dir specific attribute "path": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "use target.path instead", + ConflictsWith: []string{"target.0.path"}, }, }, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, + CustomizeDiff: resourceLibvirtPoolCustomizeDiff, } } @@ -78,11 +142,6 @@ func resourceLibvirtPoolCreate(ctx context.Context, d *schema.ResourceData, meta client := meta.(*Client) virConn := client.libvirt - poolType := d.Get("type").(string) - if poolType != "dir" { - return diag.Errorf("only storage pools of type \"dir\" are supported") - } - poolName := d.Get("name").(string) client.poolMutexKV.Lock(poolName) @@ -95,18 +154,53 @@ func resourceLibvirtPoolCreate(ctx context.Context, d *schema.ResourceData, meta } log.Printf("[DEBUG] Pool with name '%s' does not exist yet", poolName) - poolPath := d.Get("path").(string) - if poolPath == "" { - return diag.Errorf("\"path\" attribute is requires for storage pools of type \"dir\"") + var poolDef *libvirtxml.StoragePool + var skipBuild bool + + poolType := d.Get("type").(string) + if poolType != "dir" && poolType != "logical" { + return diag.Errorf("only storage pools of type \"dir\" and \"logical\" are supported") } - poolDef := libvirtxml.StoragePool{ - Type: "dir", + poolDef = &libvirtxml.StoragePool{ + Type: poolType, Name: poolName, - Target: &libvirtxml.StoragePoolTarget{ - Path: poolPath, - }, } + + if _, ok := d.GetOk("target.0"); ok { + poolDef.Target = &libvirtxml.StoragePoolTarget{ + Path: d.Get("target.0.path").(string), + } + } else { + // deprecated + poolDef.Target = &libvirtxml.StoragePoolTarget{ + Path: d.Get("path").(string), + } + } + + if _, ok := d.GetOk("source.0"); ok { + poolDef.Source = &libvirtxml.StoragePoolSource{} + + if name, ok := d.GetOk("source.0.name"); ok { + poolDef.Source.Name = name.(string) + } + + var devices []libvirtxml.StoragePoolSourceDevice + for i := 0; i < d.Get("source.0.device.#").(int); i++ { + devicePath := d.Get(fmt.Sprintf("source.0.device.%d.path", i)).(string) + devices = append(devices, libvirtxml.StoragePoolSourceDevice{Path: devicePath}) + } + + poolDef.Source.Device = devices + if len(devices) == 0 { + skipBuild = true + } + } + + if poolType == "logical" && poolDef.Source == nil { + skipBuild = true + } + data, err := xmlMarshallIndented(poolDef) if err != nil { return diag.Errorf("error serializing libvirt storage pool: %s", err) @@ -118,29 +212,26 @@ func resourceLibvirtPoolCreate(ctx context.Context, d *schema.ResourceData, meta return diag.Errorf("error applying XSLT stylesheet: %s", err) } - // create the pool pool, err := virConn.StoragePoolDefineXML(data, 0) if err != nil { return diag.Errorf("error creating libvirt storage pool: %s", err) } - err = virConn.StoragePoolBuild(pool, 0) - if err != nil { - return diag.Errorf("error building libvirt storage pool: %s", err) + if !skipBuild { + if err := virConn.StoragePoolBuild(pool, 0); err != nil { + return diag.Errorf("error building libvirt storage pool: %s", err) + } } - err = virConn.StoragePoolSetAutostart(pool, 1) - if err != nil { + if err := virConn.StoragePoolSetAutostart(pool, 1); err != nil { return diag.Errorf("error setting up libvirt storage pool: %s", err) } - err = virConn.StoragePoolCreate(pool, 0) - if err != nil { + if err := virConn.StoragePoolCreate(pool, 0); err != nil { return diag.Errorf("error starting libvirt storage pool: %s", err) } - err = virConn.StoragePoolRefresh(pool, 0) - if err != nil { + if err := virConn.StoragePoolRefresh(pool, 0); err != nil { return diag.Errorf("error refreshing libvirt storage pool: %s", err) } @@ -196,23 +287,42 @@ func resourceLibvirtPoolRead(ctx context.Context, d *schema.ResourceData, meta i return diag.Errorf("could not get a pool definition from XML for %s: %s", poolDef.Name, err) } - var poolPath string - if poolDef.Target != nil && poolDef.Target.Path != "" { - poolPath = poolDef.Target.Path - } + d.Set("type", poolDef.Type) - if poolPath == "" { - log.Printf("Pool %s has no path specified", pool.Name) - } else { - log.Printf("[DEBUG] Pool %s path: %s", pool.Name, poolPath) - d.Set("path", poolPath) + if poolDef.Source != nil { + source := map[string]interface{}{} + + if poolDef.Source.Name != "" { + source["name"] = poolDef.Source.Name + } + + if len(poolDef.Source.Device) > 0 { + var devices []interface{} + + for _, device := range poolDef.Source.Device { + deviceMap := make(map[string]interface{}) + deviceMap["path"] = device.Path + devices = append(devices, deviceMap) + } + source["device"] = devices + } + + if len(source) > 0 { + if err := d.Set("source", []interface{}{source}); err != nil { + return diag.FromErr(err) + } + } } - if poolType := poolDef.Type; poolType == "" { - log.Printf("Pool %s has no type specified", pool.Name) - } else { - log.Printf("[DEBUG] Pool %s type: %s", pool.Name, poolType) - d.Set("type", poolType) + if poolDef.Target != nil { + if _, ok := d.GetOk("path"); ok { + // old deprecated value is set + d.Set("path", poolDef.Target.Path) + } else { + target := map[string]interface{}{} + target["path"] = poolDef.Target.Path + d.Set("target", []interface{}{target}) + } } return nil @@ -248,9 +358,17 @@ func resourceLibvirtPoolDelete(ctx context.Context, d *schema.ResourceData, meta } } - err = virConn.StoragePoolDelete(pool, 0) - if err != nil { - return diag.Errorf("error deleting storage pool: %s", err) + poolDef, dia := newDefPoolFromLibvirt(virConn, pool) + if dia != nil { + return dia + } + + // if the logical pool has no source device then the volume group existed before we created the pool, so we don't delete it + if poolDef.Type == "dir" || (poolDef.Type == "logical" && poolDef.Source != nil && poolDef.Source.Device != nil) { + err = virConn.StoragePoolDelete(pool, 0) + if err != nil { + return diag.Errorf("error deleting storage pool: %s", err) + } } err = virConn.StoragePoolUndefine(pool) diff --git a/libvirt/resource_libvirt_pool_test.go b/libvirt/resource_libvirt_pool_test.go index 6abae6b21..d2fa393d7 100644 --- a/libvirt/resource_libvirt_pool_test.go +++ b/libvirt/resource_libvirt_pool_test.go @@ -1,6 +1,8 @@ package libvirt import ( + "context" + "encoding/xml" "fmt" "regexp" "testing" @@ -10,6 +12,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" + + testhelper "github.com/dmacvicar/terraform-provider-libvirt/libvirt/helper/test" ) func testAccCheckLibvirtPoolExists(name string, pool *libvirt.StoragePool) resource.TestCheckFunc { @@ -69,7 +73,7 @@ func TestAccLibvirtPool_Import(t *testing.T) { if err := composeTestImportStateCheckFunc( testImportStateCheckResourceAttr("libvirt_pool."+randomPoolResource, "name", randomPoolName), testImportStateCheckResourceAttr("libvirt_pool."+randomPoolResource, "type", "dir"), - testImportStateCheckResourceAttr("libvirt_pool."+randomPoolResource, "path", poolPath), + testImportStateCheckResourceAttr("libvirt_pool."+randomPoolResource, "target.0.path", poolPath), )(f); err != nil { return fmt.Errorf("Check InstanceState n°%d / %d error: %w", i+1, len(instanceState), err) } @@ -119,7 +123,39 @@ func testImportStateCheckResourceAttr(name string, key string, value string) Imp } } -func TestAccLibvirtPool_Basic(t *testing.T) { +func TestAccLibvirtPool_DirBasic(t *testing.T) { + var pool libvirt.StoragePool + randomPoolResource := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + randomPoolName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + poolPath := "/tmp/cluster-api-provider-libvirt-pool-" + randomPoolName + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibvirtPoolDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "libvirt_pool" "%s" { + name = "%s" + type = "dir" + target { + path = "%s" + } + }`, randomPoolResource, randomPoolName, poolPath), + Check: resource.ComposeTestCheckFunc( + testAccCheckLibvirtPoolExists("libvirt_pool."+randomPoolResource, &pool), + resource.TestCheckResourceAttr( + "libvirt_pool."+randomPoolResource, "name", randomPoolName), + resource.TestCheckResourceAttr( + "libvirt_pool."+randomPoolResource, "target.0.path", poolPath), + ), + }, + }, + }) +} + +func TestAccLibvirtPool_DirBasicDeprecated(t *testing.T) { var pool libvirt.StoragePool randomPoolResource := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) randomPoolName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) @@ -148,6 +184,133 @@ func TestAccLibvirtPool_Basic(t *testing.T) { }) } +func testAccPreCheckSupportsLogicalPool(t *testing.T) { + type storagePoolCaps struct { + Pools []struct { + Type string `xml:"type,attr"` + Supported string `xml:"supported,attr"` + } `xml:"pool"` + XMLName xml.Name `xml:"storagepoolCapabilities"` + } + + // we need the plugin configured before we can test for support for lvm pools. + diag := testAccProvider.Configure(context.Background(), terraform.NewResourceConfigRaw(nil)) + if diag.HasError() { + t.Fatal("error configuring provider") + } + + client := testAccProvider.Meta().(*Client) + + respStr, err := client.libvirt.ConnectGetStoragePoolCapabilities(0) + if err != nil { + t.Fatalf("Error getting storage pool capabilities: %s", err) + } + + var caps storagePoolCaps + err = xml.Unmarshal([]byte(respStr), &caps) + if err != nil { + t.Fatalf("Error unmarshalling storage pool capabilities: %s", err) + } + + for _, pool := range caps.Pools { + if pool.Type == "logical" && pool.Supported != "yes" { + t.Skip("Storage pool capabilities does not support logical pools") + } + } +} + +func TestAccLibvirtPool_LVMBasic(t *testing.T) { + skipIfAccDisabled(t) + + var pool libvirt.StoragePool + randomPoolResource := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + randomPoolName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + + testAccPreCheckSupportsLogicalPool(t) + + blockDev, err := testhelper.CreateTempLoopDevice(t, randomPoolName) + if err != nil { + t.Fatal(err) + } + + defer func() { + if err := blockDev.Cleanup(); err != nil { + t.Errorf("error cleaning up loop device %s: %s", blockDev, err) + } + }() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibvirtPoolDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "libvirt_pool" "%s" { + name = "%s" + type = "logical" + source { + name = "foo" + device { + path = "%s" + } + } + }`, randomPoolResource, randomPoolName, blockDev.LoopDevice), + Check: resource.ComposeTestCheckFunc( + testAccCheckLibvirtPoolExists("libvirt_pool."+randomPoolResource, &pool), + resource.TestCheckResourceAttr( + "libvirt_pool."+randomPoolResource, "name", randomPoolName), + resource.TestCheckResourceAttr( + "libvirt_pool."+randomPoolResource, "source.0.device.0.path", blockDev.LoopDevice), + ), + }, + }, + }) +} + +func TestAccLibvirtPool_LVMExisting(t *testing.T) { + skipIfAccDisabled(t) + + var pool libvirt.StoragePool + randomPoolResource := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + randomPoolName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + + testAccPreCheckSupportsLogicalPool(t) + + blockDev, err := testhelper.CreateTempLVMGroupDevice(t, randomPoolName) + if err != nil { + t.Fatal(err) + } + + defer func() { + if err := blockDev.Cleanup(); err != nil { + t.Errorf("error cleaning up loop device %s: %s", blockDev, err) + } + }() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibvirtPoolDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "libvirt_pool" "%s" { + name = "%s" + type = "logical" + }`, randomPoolResource, randomPoolName), + Check: resource.ComposeTestCheckFunc( + testAccCheckLibvirtPoolExists("libvirt_pool."+randomPoolResource, &pool), + resource.TestCheckResourceAttr( + "libvirt_pool."+randomPoolResource, "name", randomPoolName), + resource.TestCheckResourceAttr( + "libvirt_pool."+randomPoolResource, "target.0.path", fmt.Sprintf("/dev/%s", randomPoolName)), + ), + }, + }, + }) +} + // The destroy function should always handle the case where the resource might already be destroyed // (manually, for example). If the resource is already destroyed, this should not return an error. // This allows Terraform users to manually delete resources without breaking Terraform. @@ -242,7 +405,7 @@ func TestAccLibvirtPool_NoDirPath(t *testing.T) { name = "%s" type = "dir" }`, randomPoolResource, randomPoolName), - ExpectError: regexp.MustCompile(`"path" attribute is requires for storage pools of type "dir"`), + ExpectError: regexp.MustCompile(`missing storage pool target path`), }, }, }) diff --git a/libvirt/resource_libvirt_volume_test.go b/libvirt/resource_libvirt_volume_test.go index 4b198721a..cfe34fb30 100644 --- a/libvirt/resource_libvirt_volume_test.go +++ b/libvirt/resource_libvirt_volume_test.go @@ -431,7 +431,7 @@ func TestAccLibvirtVolume_Format(t *testing.T) { resource "libvirt_volume" "%s" { name = "%s" format = "raw" - size = 1073741824 + size = 10737 pool = "${libvirt_pool.%s.name}" }`, randomPoolName, randomPoolName, randomPoolPath, randomVolumeResource, randomVolumeName, randomPoolName), Check: resource.ComposeTestCheckFunc( @@ -439,7 +439,7 @@ func TestAccLibvirtVolume_Format(t *testing.T) { resource.TestCheckResourceAttr( "libvirt_volume."+randomVolumeResource, "name", randomVolumeName), resource.TestCheckResourceAttr( - "libvirt_volume."+randomVolumeResource, "size", "1073741824"), + "libvirt_volume."+randomVolumeResource, "size", "10737"), resource.TestCheckResourceAttr( "libvirt_volume."+randomVolumeResource, "format", "raw"), ), @@ -470,7 +470,7 @@ func TestAccLibvirtVolume_Import(t *testing.T) { resource "libvirt_volume" "%s" { name = "%s" format = "raw" - size = 1073741824 + size = 10737 pool = "${libvirt_pool.%s.name}" }`, randomPoolName, randomPoolName, randomPoolPath, randomVolumeResource, randomVolumeName, randomPoolName), }, @@ -482,7 +482,7 @@ func TestAccLibvirtVolume_Import(t *testing.T) { resource.TestCheckResourceAttr( "libvirt_volume."+randomVolumeResource, "name", randomVolumeName), resource.TestCheckResourceAttr( - "libvirt_volume."+randomVolumeResource, "size", "1073741824"), + "libvirt_volume."+randomVolumeResource, "size", "10737"), ), }, }, diff --git a/website/docs/r/pool.html.markdown b/website/docs/r/pool.html.markdown index 7377a15dd..316a4dc57 100644 --- a/website/docs/r/pool.html.markdown +++ b/website/docs/r/pool.html.markdown @@ -20,7 +20,9 @@ storage pools in libvirt, see [the official documentation](https://libvirt.org/f resource "libvirt_pool" "cluster" { name = "cluster" type = "dir" - path = "/home/user/cluster_storage" + target { + path = "/home/user/cluster_storage" + } } resource "libvirt_volume" "opensuse_leap" { @@ -35,9 +37,8 @@ resource "libvirt_volume" "opensuse_leap" { The following arguments are supported: * `name` - (Required) A unique name for the resource, required by libvirt. -* `type` - (Required) The type of the pool. Currently, only "dir" supported. -* `path` - (Optional) The directory where the pool will keep all its volumes. This is only relevant to (and required by) - the "dir" type pools. +* `type` - (Required) The type of the pool. Currently, "dir" and "logical" are supported. +* `path` - **Deprecated** (Optional) use `path` in the `target` block. ### Altering libvirt's generated pool XML definition