Skip to content

Commit

Permalink
Add directory-based storage pool resource
Browse files Browse the repository at this point in the history
This patch adds a resource type for directory-based libvirt storage pool.

Fixes #435.
  • Loading branch information
Zeeshan Ali authored and zeenix committed Apr 11, 2019
1 parent 6536f78 commit 23fe4aa
Show file tree
Hide file tree
Showing 6 changed files with 597 additions and 0 deletions.
15 changes: 15 additions & 0 deletions libvirt/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ func getResourceFromTerraformState(resourceName string, state *terraform.State)

// ** resource specifics helpers **

// getPoolFromTerraformState lookup pool by name and return the libvirt pool from a terraform state
func getPoolFromTerraformState(name string, state *terraform.State, virConn libvirt.Connect) (*libvirt.StoragePool, error) {
rs, err := getResourceFromTerraformState(name, state)
if err != nil {
return nil, err
}

pool, err := virConn.LookupStoragePoolByUUIDString(rs.Primary.ID)
if err != nil {
return nil, err
}
log.Printf("[DEBUG]:The ID is %s", rs.Primary.ID)
return pool, nil
}

// getVolumeFromTerraformState lookup volume by name and return the libvirt volume from a terraform state
func getVolumeFromTerraformState(name string, state *terraform.State, virConn libvirt.Connect) (*libvirt.StorageVol, error) {
rs, err := getResourceFromTerraformState(name, state)
Expand Down
115 changes: 115 additions & 0 deletions libvirt/pool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package libvirt

import (
"fmt"
"log"
"time"

"github.com/hashicorp/terraform/helper/resource"
libvirt "github.com/libvirt/libvirt-go"
)

const (
poolExistsID = "EXISTS"
poolNotExistsID = "NOT-EXISTS"
)

// poolExists returns "EXISTS" or "NOT-EXISTS" depending on the current pool existence
func poolExists(virConn *libvirt.Connect, uuid string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
pool, err := virConn.LookupStoragePoolByUUIDString(uuid)
if err != nil {
if err.(libvirt.Error).Code == libvirt.ERR_NO_STORAGE_POOL {
log.Printf("Pool %s does not exist", uuid)
return virConn, "NOT-EXISTS", nil
}
log.Printf("Pool %s: error: %s", uuid, err.(libvirt.Error).Message)
}
if pool != nil {
defer pool.Free()
}
return virConn, poolExistsID, err
}
}

// poolWaitForExists waits for a storage pool to be up and timeout after 5 minutes.
func poolWaitForExists(virConn *libvirt.Connect, uuid string) error {
log.Printf("Waiting for pool %s to be active...", uuid)
stateConf := &resource.StateChangeConf{
Pending: []string{poolNotExistsID},
Target: []string{poolExistsID},
Refresh: poolExists(virConn, uuid),
Timeout: 1 * time.Minute,
Delay: 5 * time.Second,
MinTimeout: 3 * time.Second,
}

if _, err := stateConf.WaitForState(); err != nil {
log.Printf("%s", err)
return fmt.Errorf("Unexpected error during pool creation operation. The operation did not complete successfully.")
}
return nil
}

// poolWaitDeleted waits for a storage pool to be removed
func poolWaitDeleted(virConn *libvirt.Connect, uuid string) error {
log.Printf("Waiting for pool %s to be deleted...", uuid)
stateConf := &resource.StateChangeConf{
Pending: []string{poolExistsID},
Target: []string{poolNotExistsID},
Refresh: poolExists(virConn, uuid),
Timeout: 1 * time.Minute,
Delay: 5 * time.Second,
MinTimeout: 3 * time.Second,
}

if _, err := stateConf.WaitForState(); err != nil {
log.Printf("%s", err)
return fmt.Errorf("Unexpected error during pool destroy operation. The pool was not deleted.")
}
return nil
}

// deletePool deletes the pool identified by `uuid` from libvirt
func deletePool(client *Client, uuid string) error {
virConn := client.libvirt
if virConn == nil {
return fmt.Errorf(LibVirtConIsNil)
}

pool, err := virConn.LookupStoragePoolByUUIDString(uuid)
if err != nil {
return fmt.Errorf("error retrieving storage pool info: %s", err)
}

poolName, err := pool.GetName()
if err != nil {
return fmt.Errorf("error retrieving storage pool name: %s", err)
}
client.poolMutexKV.Lock(poolName)
defer client.poolMutexKV.Unlock(poolName)

info, err := pool.GetInfo()
if err != nil {
return fmt.Errorf("error retrieving storage pool info: %s", err)
}

if info.State != libvirt.STORAGE_POOL_INACTIVE {
err := pool.Destroy()
if err != nil {
return fmt.Errorf("error deleting storage pool: %s", err)
}
}

err = pool.Delete(0)
if err != nil {
return fmt.Errorf("error deleting storage pool: %s", err)
}

err = pool.Undefine()
if err != nil {
return fmt.Errorf("error deleting storage pool: %s", err)
}

return poolWaitDeleted(client.libvirt, uuid)
}
1 change: 1 addition & 0 deletions libvirt/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func Provider() terraform.ResourceProvider {
"libvirt_domain": resourceLibvirtDomain(),
"libvirt_volume": resourceLibvirtVolume(),
"libvirt_network": resourceLibvirtNetwork(),
"libvirt_dir_pool": resourceLibvirtDirPool(),
"libvirt_cloudinit_disk": resourceCloudInitDisk(),
"libvirt_ignition": resourceIgnition(),
},
Expand Down
239 changes: 239 additions & 0 deletions libvirt/resource_libvirt_dir_pool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package libvirt

import (
"encoding/xml"
"fmt"
"log"

"github.com/hashicorp/terraform/helper/schema"
libvirt "github.com/libvirt/libvirt-go"
"github.com/libvirt/libvirt-go-xml"
)

func resourceLibvirtDirPool() *schema.Resource {
return &schema.Resource{
Create: resourceLibvirtDirPoolCreate,
Read: resourceLibvirtDirPoolRead,
Delete: resourceLibvirtDirPoolDelete,
Exists: resourceLibvirtDirPoolExists,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"path": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"capacity": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
},
"allocation": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
},
"available": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"xml": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"xslt": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
},
},
},
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
}
}

func resourceLibvirtDirPoolCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client)
if client.libvirt == nil {
return fmt.Errorf(LibVirtConIsNil)
}

poolName := d.Get("name").(string)

client.poolMutexKV.Lock(poolName)
defer client.poolMutexKV.Unlock(poolName)

// Check whether the storage pool already exists. Its name needs to be
// unique.
if _, err := client.libvirt.LookupStoragePoolByName(poolName); err == nil {
return fmt.Errorf("storage pool '%s' already exists", poolName)
}
log.Printf("[DEBUG] Pool with name '%s' does not exist yet", poolName)

poolPath := d.Get("path").(string)
poolDef := libvirtxml.StoragePool{
Type: "dir",
Name: poolName,
Target: &libvirtxml.StoragePoolTarget{
Path: poolPath,
},
}
data, err := xmlMarshallIndented(poolDef)
if err != nil {
return fmt.Errorf("Error serializing libvirt storage pool: %s", err)
}
log.Printf("[DEBUG] Generated XML for libvirt storage pool:\n%s", data)

data, err = transformResourceXML(data, d)
if err != nil {
return fmt.Errorf("Error applying XSLT stylesheet: %s", err)
}

// create the pool
pool, err := client.libvirt.StoragePoolDefineXML(data, 0)
if err != nil {
return fmt.Errorf("Error creating libvirt storage pool: %s", err)
}
defer pool.Free()

err = pool.Build(0)
if err != nil {
return fmt.Errorf("Error building libvirt storage pool: %s", err)
}

err = pool.SetAutostart(true)
if err != nil {
return fmt.Errorf("Error setting up libvirt storage pool: %s", err)
}

err = pool.Create(0)
if err != nil {
return fmt.Errorf("Error starting libvirt storage pool: %s", err)
}

err = pool.Refresh(0)
if err != nil {
return fmt.Errorf("Error refreshing libvirt storage pool: %s", err)
}

id, err := pool.GetUUIDString()
if err != nil {
return fmt.Errorf("Error retrieving libvirt pool id: %s", err)
}
d.SetId(id)

// make sure we record the id even if the rest of this gets interrupted
d.Partial(true)
d.Set("id", id)
d.SetPartial("id")
d.Partial(false)

log.Printf("[INFO] Pool ID: %s", d.Id())

if err := poolWaitForExists(client.libvirt, id); err != nil {
return err
}

return resourceLibvirtDirPoolRead(d, meta)
}

func resourceLibvirtDirPoolRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client)
virConn := client.libvirt
if virConn == nil {
return fmt.Errorf(LibVirtConIsNil)
}

pool, err := virConn.LookupStoragePoolByUUIDString(d.Id())
if pool == nil {
log.Printf("storage pool '%s' may have been deleted outside Terraform", d.Id())
d.SetId("")
return nil
}
defer pool.Free()

poolName, err := pool.GetName()
if err != nil {
return fmt.Errorf("error retrieving pool name: %s", err)
}
d.Set("name", poolName)

info, err := pool.GetInfo()
if err != nil {
return fmt.Errorf("error retrieving pool info: %s", err)
}
d.Set("capacity", info.Capacity)
d.Set("allocation", info.Allocation)
d.Set("available", info.Available)

poolDefXML, err := pool.GetXMLDesc(0)
if err != nil {
return fmt.Errorf("could not get XML description for pool %s: %s", poolName, err)
}

var poolDef libvirtxml.StoragePool
err = xml.Unmarshal([]byte(poolDefXML), &poolDef)
if err != nil {
return fmt.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
}

if poolPath == "" {
log.Printf("Pool %s has no path specified", poolName)
} else {
log.Printf("[DEBUG] Pool %s path: %s", poolName, poolPath)
d.Set("path", poolPath)
}

return nil
}

func resourceLibvirtDirPoolDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client)
if client.libvirt == nil {
return fmt.Errorf(LibVirtConIsNil)
}

return deletePool(client, d.Id())
}

func resourceLibvirtDirPoolExists(d *schema.ResourceData, meta interface{}) (bool, error) {
log.Printf("[DEBUG] Check if resource libvirt_dir_pool exists")
client := meta.(*Client)
virConn := client.libvirt
if virConn == nil {
return false, fmt.Errorf(LibVirtConIsNil)
}

pool, err := virConn.LookupStoragePoolByUUIDString(d.Id())
if err != nil {
virErr := err.(libvirt.Error)
if virErr.Code != libvirt.ERR_NO_STORAGE_POOL {
return false, fmt.Errorf("Can't retrieve pool %s", d.Id())
}
// does not exist, but no error
return false, nil
}
defer pool.Free()

return true, nil
}

0 comments on commit 23fe4aa

Please sign in to comment.