terraform/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfi...

509 lines
14 KiB
Go

package openstack
import (
"fmt"
"log"
"time"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors"
"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages"
"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares"
)
const (
// major share functionality appeared in 2.14
minManilaShareMicroversion = "2.14"
)
func resourceSharedFilesystemShareV2() *schema.Resource {
return &schema.Resource{
Create: resourceSharedFilesystemShareV2Create,
Read: resourceSharedFilesystemShareV2Read,
Update: resourceSharedFilesystemShareV2Update,
Delete: resourceSharedFilesystemShareV2Delete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(10 * time.Minute),
Update: schema.DefaultTimeout(10 * time.Minute),
Delete: schema.DefaultTimeout(10 * time.Minute),
},
Schema: map[string]*schema.Schema{
"region": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"project_id": {
Type: schema.TypeString,
Computed: true,
},
"name": {
Type: schema.TypeString,
Required: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"share_proto": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{
"NFS", "CIFS", "CEPHFS", "GLUSTERFS", "HDFS", "MAPRFS",
}, true),
},
"size": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntAtLeast(1),
},
"share_type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"snapshot_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"is_public": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"metadata": {
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
},
"share_network_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"availability_zone": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"export_locations": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"path": {
Type: schema.TypeString,
Computed: true,
},
"preferred": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
"has_replicas": {
Type: schema.TypeBool,
Computed: true,
},
"host": {
Type: schema.TypeString,
Computed: true,
},
"replication_type": {
Type: schema.TypeString,
Computed: true,
},
"share_server_id": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceSharedFilesystemShareV2Create(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config))
if err != nil {
return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err)
}
sfsClient.Microversion = minManilaShareMicroversion
isPublic := d.Get("is_public").(bool)
metadataRaw := d.Get("metadata").(map[string]interface{})
metadata := make(map[string]string, len(metadataRaw))
for k, v := range metadataRaw {
if stringVal, ok := v.(string); ok {
metadata[k] = stringVal
}
}
createOpts := shares.CreateOpts{
Name: d.Get("name").(string),
Description: d.Get("description").(string),
ShareProto: d.Get("share_proto").(string),
Size: d.Get("size").(int),
SnapshotID: d.Get("snapshot_id").(string),
IsPublic: &isPublic,
Metadata: metadata,
ShareNetworkID: d.Get("share_network_id").(string),
AvailabilityZone: d.Get("availability_zone").(string),
}
if v, ok := d.GetOkExists("share_type"); ok {
createOpts.ShareType = v.(string)
}
log.Printf("[DEBUG] Create Options: %#v", createOpts)
timeout := d.Timeout(schema.TimeoutCreate)
log.Printf("[DEBUG] Attempting to create share")
var share *shares.Share
err = resource.Retry(timeout, func() *resource.RetryError {
share, err = shares.Create(sfsClient, createOpts).Extract()
if err != nil {
return checkForRetryableError(err)
}
return nil
})
if err != nil {
detailedErr := errors.ErrorDetails{}
e := errors.ExtractErrorInto(err, &detailedErr)
if e != nil {
return fmt.Errorf("Error creating share: %s: %s", err, e)
}
for k, msg := range detailedErr {
return fmt.Errorf("Error creating share: %s (%d): %s", k, msg.Code, msg.Message)
}
}
d.SetId(share.ID)
// Wait for share to become active before continuing
err = waitForSFV2Share(sfsClient, share.ID, "available", []string{"creating", "manage_starting"}, timeout)
if err != nil {
return err
}
return resourceSharedFilesystemShareV2Read(d, meta)
}
func resourceSharedFilesystemShareV2Read(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config))
if err != nil {
return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err)
}
sfsClient.Microversion = minManilaShareMicroversion
share, err := shares.Get(sfsClient, d.Id()).Extract()
if err != nil {
return CheckDeleted(d, err, "share")
}
log.Printf("[DEBUG] Retrieved share %s: %#v", d.Id(), share)
exportLocationsRaw, err := shares.GetExportLocations(sfsClient, d.Id()).Extract()
if err != nil {
return fmt.Errorf("Failed to retrieve share's export_locations %s: %s", d.Id(), err)
}
log.Printf("[DEBUG] Retrieved share's export_locations %s: %#v", d.Id(), exportLocationsRaw)
var exportLocations []map[string]string
for _, v := range exportLocationsRaw {
exportLocations = append(exportLocations, map[string]string{
"path": v.Path,
"preferred": fmt.Sprint(v.Preferred),
})
}
if err = d.Set("export_locations", exportLocations); err != nil {
log.Printf("[DEBUG] Unable to set export_locations: %s", err)
}
d.Set("name", share.Name)
d.Set("description", share.Description)
d.Set("share_proto", share.ShareProto)
d.Set("size", share.Size)
d.Set("share_type", share.ShareTypeName)
d.Set("snapshot_id", share.SnapshotID)
d.Set("is_public", share.IsPublic)
d.Set("metadata", share.Metadata)
d.Set("share_network_id", share.ShareNetworkID)
d.Set("availability_zone", share.AvailabilityZone)
// Computed
d.Set("region", GetRegion(d, config))
d.Set("project_id", share.ProjectID)
d.Set("has_replicas", share.HasReplicas)
d.Set("host", share.Host)
d.Set("replication_type", share.ReplicationType)
d.Set("share_server_id", share.ShareServerID)
return nil
}
func resourceSharedFilesystemShareV2Update(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config))
if err != nil {
return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err)
}
sfsClient.Microversion = minManilaShareMicroversion
timeout := d.Timeout(schema.TimeoutUpdate)
var updateOpts shares.UpdateOpts
if d.HasChange("name") {
name := d.Get("name").(string)
updateOpts.DisplayName = &name
}
if d.HasChange("description") {
description := d.Get("description").(string)
updateOpts.DisplayDescription = &description
}
if d.HasChange("is_public") {
isPublic := d.Get("is_public").(bool)
updateOpts.IsPublic = &isPublic
}
if updateOpts != (shares.UpdateOpts{}) {
// Wait for share to become active before continuing
err = waitForSFV2Share(sfsClient, d.Id(), "available", []string{"creating", "manage_starting", "extending", "shrinking"}, timeout)
if err != nil {
return err
}
log.Printf("[DEBUG] Attempting to update share")
err = resource.Retry(timeout, func() *resource.RetryError {
_, err := shares.Update(sfsClient, d.Id(), updateOpts).Extract()
if err != nil {
return checkForRetryableError(err)
}
return nil
})
if err != nil {
detailedErr := errors.ErrorDetails{}
e := errors.ExtractErrorInto(err, &detailedErr)
if e != nil {
return fmt.Errorf("Error updating %s share: %s: %s", d.Id(), err, e)
}
for k, msg := range detailedErr {
return fmt.Errorf("Error updating %s share: %s (%d): %s", d.Id(), k, msg.Code, msg.Message)
}
}
// Wait for share to become active before continuing
err = waitForSFV2Share(sfsClient, d.Id(), "available", []string{"creating", "manage_starting", "extending", "shrinking"}, timeout)
if err != nil {
return err
}
}
if d.HasChange("size") {
var pending []string
oldSize, newSize := d.GetChange("size")
if newSize.(int) > oldSize.(int) {
pending = append(pending, "extending")
resizeOpts := shares.ExtendOpts{NewSize: newSize.(int)}
log.Printf("[DEBUG] Resizing share %s with options: %#v", d.Id(), resizeOpts)
err = resource.Retry(timeout, func() *resource.RetryError {
err := shares.Extend(sfsClient, d.Id(), resizeOpts).Err
log.Printf("[DEBUG] Resizing share %s with options: %#v", d.Id(), resizeOpts)
if err != nil {
return checkForRetryableError(err)
}
return nil
})
} else if newSize.(int) < oldSize.(int) {
pending = append(pending, "shrinking")
resizeOpts := shares.ShrinkOpts{NewSize: newSize.(int)}
log.Printf("[DEBUG] Resizing share %s with options: %#v", d.Id(), resizeOpts)
err = resource.Retry(timeout, func() *resource.RetryError {
err := shares.Shrink(sfsClient, d.Id(), resizeOpts).Err
log.Printf("[DEBUG] Resizing share %s with options: %#v", d.Id(), resizeOpts)
if err != nil {
return checkForRetryableError(err)
}
return nil
})
}
if err != nil {
detailedErr := errors.ErrorDetails{}
e := errors.ExtractErrorInto(err, &detailedErr)
if e != nil {
return fmt.Errorf("Unable to resize %s share: %s: %s", d.Id(), err, e)
}
for k, msg := range detailedErr {
return fmt.Errorf("Unable to resize %s share: %s (%d): %s", d.Id(), k, msg.Code, msg.Message)
}
}
// Wait for share to become active before continuing
err = waitForSFV2Share(sfsClient, d.Id(), "available", pending, timeout)
if err != nil {
return err
}
}
return resourceSharedFilesystemShareV2Read(d, meta)
}
func resourceSharedFilesystemShareV2Delete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config))
if err != nil {
return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err)
}
timeout := d.Timeout(schema.TimeoutDelete)
log.Printf("[DEBUG] Attempting to delete share %s", d.Id())
err = resource.Retry(timeout, func() *resource.RetryError {
err = shares.Delete(sfsClient, d.Id()).ExtractErr()
if err != nil {
return checkForRetryableError(err)
}
return nil
})
if err != nil {
e := CheckDeleted(d, err, "")
if e == nil {
return nil
}
detailedErr := errors.ErrorDetails{}
e = errors.ExtractErrorInto(err, &detailedErr)
if e != nil {
return fmt.Errorf("Unable to delete %s share: %s: %s", d.Id(), err, e)
}
for k, msg := range detailedErr {
return fmt.Errorf("Unable to delete %s share: %s (%d): %s", d.Id(), k, msg.Code, msg.Message)
}
}
// Wait for share to become deleted before continuing
pending := []string{"", "deleting", "available"}
err = waitForSFV2Share(sfsClient, d.Id(), "deleted", pending, timeout)
if err != nil {
return err
}
return nil
}
// Full list of the share statuses: https://developer.openstack.org/api-ref/shared-file-system/#shares
func waitForSFV2Share(sfsClient *gophercloud.ServiceClient, id string, target string, pending []string, timeout time.Duration) error {
log.Printf("[DEBUG] Waiting for share %s to become %s.", id, target)
stateConf := &resource.StateChangeConf{
Target: []string{target},
Pending: pending,
Refresh: resourceSFV2ShareRefreshFunc(sfsClient, id),
Timeout: timeout,
Delay: 1 * time.Second,
MinTimeout: 1 * time.Second,
}
_, err := stateConf.WaitForState()
if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok {
switch target {
case "deleted":
return nil
default:
return fmt.Errorf("Error: share %s not found: %s", id, err)
}
}
errorMessage := fmt.Sprintf("Error waiting for share %s to become %s", id, target)
msg := resourceSFSV2ShareManilaMessage(sfsClient, id)
if msg == nil {
return fmt.Errorf("%s: %s", errorMessage, err)
}
return fmt.Errorf("%s: %s: the latest manila message (%s): %s", errorMessage, err, msg.CreatedAt, msg.UserMessage)
}
return nil
}
func resourceSFV2ShareRefreshFunc(sfsClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
share, err := shares.Get(sfsClient, id).Extract()
if err != nil {
return nil, "", err
}
return share, share.Status, nil
}
}
func resourceSFSV2ShareManilaMessage(sfsClient *gophercloud.ServiceClient, id string) *messages.Message {
// we can simply set this, because this function is called after the error occurred
sfsClient.Microversion = "2.37"
listOpts := messages.ListOpts{
ResourceID: id,
SortKey: "created_at",
SortDir: "desc",
Limit: 1,
}
allPages, err := messages.List(sfsClient, listOpts).AllPages()
if err != nil {
log.Printf("[DEBUG] Unable to retrieve messages: %v", err)
return nil
}
allMessages, err := messages.ExtractMessages(allPages)
if err != nil {
log.Printf("[DEBUG] Unable to extract messages: %v", err)
return nil
}
if len(allMessages) == 0 {
log.Printf("[DEBUG] No messages found")
return nil
}
return &allMessages[0]
}