Skip to content

Commit

Permalink
Manila: add share acl resource (#526)
Browse files Browse the repository at this point in the history
* 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
kayrus authored and jtopjian committed Jan 5, 2019
1 parent e29872c commit 459c6b6
Show file tree
Hide file tree
Showing 15 changed files with 2,420 additions and 172 deletions.
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 openstack/import_openstack_sharedfilesystem_share_v2_test.go
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,
},
},
})
}
2 changes: 2 additions & 0 deletions openstack/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ func Provider() terraform.ResourceProvider {
"openstack_vpnaas_site_connection_v2": resourceSiteConnectionV2(),
"openstack_sharedfilesystem_securityservice_v2": resourceSharedFilesystemSecurityServiceV2(),
"openstack_sharedfilesystem_sharenetwork_v2": resourceSharedFilesystemShareNetworkV2(),
"openstack_sharedfilesystem_share_v2": resourceSharedFilesystemShareV2(),
"openstack_sharedfilesystem_share_access_v2": resourceSharedFilesystemShareAccessV2(),
},

ConfigureFunc: configureProvider,
Expand Down
282 changes: 282 additions & 0 deletions openstack/resource_openstack_sharedfilesystem_share_access_v2.go
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{}
}
}
Loading

0 comments on commit 459c6b6

Please sign in to comment.