-
Notifications
You must be signed in to change notification settings - Fork 456
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add directory-based storage pool resource
This patch adds a resource type for directory-based libvirt storage pool. Fixes #435.
- Loading branch information
Showing
6 changed files
with
595 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
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 { | ||
return fmt.Errorf("Error waiting for pool to reach EXISTS state: %s", err) | ||
} | ||
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 { | ||
return fmt.Errorf("Error waiting for pool to reach NOT-EXISTS state: %s", err) | ||
} | ||
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.