-
Notifications
You must be signed in to change notification settings - Fork 359
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Manila: add share acl resource (#526)
* Manila: add share resource * Manila: comment out the shrink test * Manila: minor code review changes * Manila: add detailed error handling * Manila: add share_access resource (ACL) * Manila: minor code review and acl import support * Manila: add share access detailed error handling
- Loading branch information
Showing
15 changed files
with
2,420 additions
and
172 deletions.
There are no files selected for viewing
48 changes: 48 additions & 0 deletions
48
openstack/import_openstack_sharedfilesystem_share_access_v2_test.go
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,48 @@ | ||
package openstack | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform/helper/resource" | ||
"github.com/hashicorp/terraform/terraform" | ||
) | ||
|
||
func TestAccSFSV2ShareAccess_importBasic(t *testing.T) { | ||
shareName := "openstack_sharedfilesystem_share_v2.share_1" | ||
shareAccessName := "openstack_sharedfilesystem_share_access_v2.share_access_1" | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheckSFS(t) }, | ||
Providers: testAccProviders, | ||
CheckDestroy: testAccCheckSFSV2ShareAccessDestroy, | ||
Steps: []resource.TestStep{ | ||
resource.TestStep{ | ||
Config: testAccSFSV2ShareAccessConfig_basic, | ||
}, | ||
|
||
resource.TestStep{ | ||
ResourceName: shareAccessName, | ||
ImportState: true, | ||
ImportStateVerify: true, | ||
ImportStateIdFunc: testAccSFSV2ShareAccessImportID(shareName, shareAccessName), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccSFSV2ShareAccessImportID(shareResource, shareAccessResource string) resource.ImportStateIdFunc { | ||
return func(s *terraform.State) (string, error) { | ||
share, ok := s.RootModule().Resources[shareResource] | ||
if !ok { | ||
return "", fmt.Errorf("Share not found: %s", shareResource) | ||
} | ||
|
||
shareAccess, ok := s.RootModule().Resources[shareAccessResource] | ||
if !ok { | ||
return "", fmt.Errorf("Share access not found: %s", shareAccessResource) | ||
} | ||
|
||
return fmt.Sprintf("%s/%s", share.Primary.ID, shareAccess.Primary.ID), nil | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
openstack/import_openstack_sharedfilesystem_share_v2_test.go
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,28 @@ | ||
package openstack | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform/helper/resource" | ||
) | ||
|
||
func TestAccSFSV2Share_importBasic(t *testing.T) { | ||
resourceName := "openstack_sharedfilesystem_share_v2.share_1" | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheckSFS(t) }, | ||
Providers: testAccProviders, | ||
CheckDestroy: testAccCheckSFSV2ShareDestroy, | ||
Steps: []resource.TestStep{ | ||
resource.TestStep{ | ||
Config: testAccSFSV2ShareConfig_basic, | ||
}, | ||
|
||
resource.TestStep{ | ||
ResourceName: resourceName, | ||
ImportState: true, | ||
ImportStateVerify: true, | ||
}, | ||
}, | ||
}) | ||
} |
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
282 changes: 282 additions & 0 deletions
282
openstack/resource_openstack_sharedfilesystem_share_access_v2.go
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,282 @@ | ||
package openstack | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"strings" | ||
"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/shares" | ||
) | ||
|
||
func resourceSharedFilesystemShareAccessV2() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceSharedFilesystemShareAccessV2Create, | ||
Read: resourceSharedFilesystemShareAccessV2Read, | ||
Delete: resourceSharedFilesystemShareAccessV2Delete, | ||
Importer: &schema.ResourceImporter{ | ||
resourceSharedFilesystemShareAccessV2Import, | ||
}, | ||
|
||
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{ | ||
"share_id": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"access_type": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
ValidateFunc: validation.StringInSlice([]string{ | ||
"ip", "user", "cert", | ||
}, true), | ||
}, | ||
|
||
"access_to": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"access_level": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
ValidateFunc: validation.StringInSlice([]string{ | ||
"rw", "ro", | ||
}, true), | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceSharedFilesystemShareAccessV2Create(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 = minManilaMicroversion | ||
|
||
shareID := d.Get("share_id").(string) | ||
|
||
grantOpts := shares.GrantAccessOpts{ | ||
AccessType: d.Get("access_type").(string), | ||
AccessTo: d.Get("access_to").(string), | ||
AccessLevel: d.Get("access_level").(string), | ||
} | ||
|
||
log.Printf("[DEBUG] Create Options: %#v", grantOpts) | ||
|
||
timeout := d.Timeout(schema.TimeoutCreate) | ||
|
||
log.Printf("[DEBUG] Attempting to grant access") | ||
var access *shares.AccessRight | ||
err = resource.Retry(timeout, func() *resource.RetryError { | ||
access, err = shares.GrantAccess(sfsClient, shareID, grantOpts).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 granting access: %s: %s", err, e) | ||
} | ||
for k, msg := range detailedErr { | ||
return fmt.Errorf("Error granting access: %s (%d): %s", k, msg.Code, msg.Message) | ||
} | ||
} | ||
|
||
d.SetId(access.ID) | ||
|
||
pending := []string{"new", "queued_to_apply", "applying"} | ||
// Wait for access to become active before continuing | ||
err = waitForSFV2Access(sfsClient, shareID, access.ID, "active", pending, timeout) | ||
if err != nil { | ||
return fmt.Errorf("Error waiting for OpenStack share ACL on %s to be applied: %s", shareID, err) | ||
} | ||
|
||
return resourceSharedFilesystemShareAccessV2Read(d, meta) | ||
} | ||
|
||
func resourceSharedFilesystemShareAccessV2Read(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 = minManilaMicroversion | ||
|
||
shareID := d.Get("share_id").(string) | ||
|
||
access, err := shares.ListAccessRights(sfsClient, shareID).Extract() | ||
if err != nil { | ||
return CheckDeleted(d, err, "share") | ||
} | ||
|
||
for _, v := range access { | ||
if v.ID == d.Id() { | ||
log.Printf("[DEBUG] Retrieved %s share ACL: %#v", d.Id(), v) | ||
|
||
d.Set("access_type", v.AccessType) | ||
d.Set("access_to", v.AccessTo) | ||
d.Set("access_level", v.AccessLevel) | ||
|
||
return nil | ||
} | ||
} | ||
|
||
log.Printf("[DEBUG] Unable to find %s share access", d.Id()) | ||
d.SetId("") | ||
|
||
return nil | ||
} | ||
|
||
func resourceSharedFilesystemShareAccessV2Delete(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 = minManilaMicroversion | ||
|
||
shareID := d.Get("share_id").(string) | ||
|
||
revokeOpts := shares.RevokeAccessOpts{AccessID: d.Id()} | ||
|
||
timeout := d.Timeout(schema.TimeoutDelete) | ||
|
||
log.Printf("[DEBUG] Attempting to revoke access %s", d.Id()) | ||
err = resource.Retry(timeout, func() *resource.RetryError { | ||
err = shares.RevokeAccess(sfsClient, shareID, revokeOpts).ExtractErr() | ||
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 waiting for OpenStack share ACL on %s to be removed: %s: %s", shareID, err, e) | ||
} | ||
for k, msg := range detailedErr { | ||
return fmt.Errorf("Error waiting for OpenStack share ACL on %s to be removed: %s (%d): %s", shareID, k, msg.Code, msg.Message) | ||
} | ||
} | ||
|
||
// Wait for access to become deleted before continuing | ||
pending := []string{"new", "queued_to_deny", "denying"} | ||
err = waitForSFV2Access(sfsClient, shareID, d.Id(), "denied", pending, timeout) | ||
if err != nil { | ||
return fmt.Errorf("Error waiting for OpenStack share ACL on %s to be removed: %s", shareID, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceSharedFilesystemShareAccessV2Import(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { | ||
parts := strings.SplitN(d.Id(), "/", 2) | ||
if len(parts) != 2 { | ||
err := fmt.Errorf("Invalid format specified for Openstack share ACL. Format must be <share id>/<ACL id>") | ||
return nil, err | ||
} | ||
|
||
config := meta.(*Config) | ||
sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) | ||
if err != nil { | ||
return nil, fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) | ||
} | ||
|
||
sfsClient.Microversion = minManilaMicroversion | ||
|
||
shareID := parts[0] | ||
accessID := parts[1] | ||
|
||
access, err := shares.ListAccessRights(sfsClient, shareID).Extract() | ||
if err != nil { | ||
return nil, fmt.Errorf("Unable to get %s Openstack share and its ACL's: %s", shareID, err) | ||
} | ||
|
||
for _, v := range access { | ||
if v.ID == accessID { | ||
log.Printf("[DEBUG] Retrieved %s share ACL: %#v", accessID, v) | ||
|
||
d.SetId(accessID) | ||
d.Set("share_id", shareID) | ||
d.Set("access_type", v.AccessType) | ||
d.Set("access_to", v.AccessTo) | ||
d.Set("access_level", v.AccessLevel) | ||
|
||
return []*schema.ResourceData{d}, nil | ||
} | ||
} | ||
|
||
return nil, fmt.Errorf("[DEBUG] Unable to find %s share access", accessID) | ||
} | ||
|
||
// Full list of the share access statuses: https://developer.openstack.org/api-ref/shared-file-system/?expanded=list-services-detail,list-access-rules-detail#list-access-rules | ||
func waitForSFV2Access(sfsClient *gophercloud.ServiceClient, shareID string, id string, target string, pending []string, timeout time.Duration) error { | ||
log.Printf("[DEBUG] Waiting for access %s to become %s.", id, target) | ||
|
||
stateConf := &resource.StateChangeConf{ | ||
Target: []string{target}, | ||
Pending: pending, | ||
Refresh: resourceSFV2AccessRefreshFunc(sfsClient, shareID, 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 "denied": | ||
return nil | ||
default: | ||
return fmt.Errorf("Error: access %s not found: %s", id, err) | ||
} | ||
} | ||
return fmt.Errorf("Error waiting for access %s to become %s: %s", id, target, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceSFV2AccessRefreshFunc(sfsClient *gophercloud.ServiceClient, shareID string, id string) resource.StateRefreshFunc { | ||
return func() (interface{}, string, error) { | ||
access, err := shares.ListAccessRights(sfsClient, shareID).Extract() | ||
if err != nil { | ||
return nil, "", err | ||
} | ||
for _, v := range access { | ||
if v.ID == id { | ||
return v, v.State, nil | ||
} | ||
} | ||
return nil, "", gophercloud.ErrDefault404{} | ||
} | ||
} |
Oops, something went wrong.