diff --git a/go.mod b/go.mod index ac4a18aa2..87c93f3eb 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/terraform-provider-openstack/terraform-provider-openstack go 1.17 require ( - github.com/gophercloud/gophercloud v0.23.0 + github.com/gophercloud/gophercloud v0.23.1-0.20211129155426-97dea84b37a5 github.com/gophercloud/utils v0.0.0-20210909165623-d7085207ff6d github.com/hashicorp/terraform-plugin-sdk/v2 v2.7.1 github.com/mitchellh/go-homedir v1.1.0 diff --git a/go.sum b/go.sum index 1280c1583..1f2c728f4 100644 --- a/go.sum +++ b/go.sum @@ -157,6 +157,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gophercloud/gophercloud v0.20.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= github.com/gophercloud/gophercloud v0.23.0 h1:I3P10oKlGu3DHP9PrEWMr1ya+/+3Rc9uRHNkRZ9wO7g= github.com/gophercloud/gophercloud v0.23.0/go.mod h1:MRw6uyLj8uCGbIvBlqL7QW67t0QtNZnzydUzewo1Ioc= +github.com/gophercloud/gophercloud v0.23.1-0.20211129155426-97dea84b37a5 h1:EckX+4F7gL2lJgrQaqmZYomKv5EupJgzviarTTxDg4g= +github.com/gophercloud/gophercloud v0.23.1-0.20211129155426-97dea84b37a5/go.mod h1:MRw6uyLj8uCGbIvBlqL7QW67t0QtNZnzydUzewo1Ioc= github.com/gophercloud/utils v0.0.0-20210909165623-d7085207ff6d h1:0Wsi5dvUuPF6dVn/CNfEA4xLxmaEtOt7tV2HD16xIf8= github.com/gophercloud/utils v0.0.0-20210909165623-d7085207ff6d/go.mod h1:qOGlfG6OIJ193/c3Xt/XjOfHataNZdQcVgiu93LxBUM= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= diff --git a/openstack/import_openstack_blockstorage_qos_v3_test.go b/openstack/import_openstack_blockstorage_qos_v3_test.go new file mode 100644 index 000000000..b65d3df1c --- /dev/null +++ b/openstack/import_openstack_blockstorage_qos_v3_test.go @@ -0,0 +1,31 @@ +package openstack + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccBlockStorageQosV3_importBasic(t *testing.T) { + resourceName := "openstack_blockstorage_qos_v3.qos" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAdminOnly(t) + }, + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckBlockStorageQosV3Destroy, + Steps: []resource.TestStep{ + { + Config: testAccBlockStorageQosV3Basic, + }, + + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/openstack/provider.go b/openstack/provider.go index caa979573..06ab8dda7 100644 --- a/openstack/provider.go +++ b/openstack/provider.go @@ -297,6 +297,7 @@ func Provider() *schema.Provider { }, ResourcesMap: map[string]*schema.Resource{ + "openstack_blockstorage_qos_v3": resourceBlockStorageQosV3(), "openstack_blockstorage_quotaset_v2": resourceBlockStorageQuotasetV2(), "openstack_blockstorage_quotaset_v3": resourceBlockStorageQuotasetV3(), "openstack_blockstorage_volume_v1": resourceBlockStorageVolumeV1(), diff --git a/openstack/resource_openstack_blockstorage_qos_v3.go b/openstack/resource_openstack_blockstorage_qos_v3.go new file mode 100644 index 000000000..f9b22aba4 --- /dev/null +++ b/openstack/resource_openstack_blockstorage_qos_v3.go @@ -0,0 +1,173 @@ +package openstack + +import ( + "context" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/qos" +) + +func resourceBlockStorageQosV3() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceBlockStorageQosV3Create, + ReadContext: resourceBlockStorageQosV3Read, + UpdateContext: resourceBlockStorageQosV3Update, + DeleteContext: resourceBlockStorageQosV3Delete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "consumer": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "front-end", "back-end", "both", + }, false), + }, + + "specs": { + Type: schema.TypeMap, + Optional: true, + }, + }, + } +} + +func resourceBlockStorageQosV3Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*Config) + blockStorageClient, err := config.BlockStorageV3Client(GetRegion(d, config)) + if err != nil { + return diag.Errorf("Error creating OpenStack block storage client: %s", err) + } + + name := d.Get("name").(string) + consumer := qos.QoSConsumer(d.Get("consumer").(string)) + specs := d.Get("specs").(map[string]interface{}) + createOpts := qos.CreateOpts{ + Name: name, + Consumer: consumer, + Specs: expandToMapStringString(specs), + } + + log.Printf("[DEBUG] openstack_blockstorage_qos_v3 create options: %#v", createOpts) + qosRes, err := qos.Create(blockStorageClient, &createOpts).Extract() + if err != nil { + return diag.Errorf("Error creating openstack_blockstorage_qos_v3 %s: %s", name, err) + } + + d.SetId(qosRes.ID) + + return resourceBlockStorageQosV3Read(ctx, d, meta) +} + +func resourceBlockStorageQosV3Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*Config) + blockStorageClient, err := config.BlockStorageV3Client(GetRegion(d, config)) + if err != nil { + return diag.Errorf("Error creating OpenStack block storage client: %s", err) + } + + qosRes, err := qos.Get(blockStorageClient, d.Id()).Extract() + if err != nil { + return diag.FromErr(CheckDeleted(d, err, "Error retrieving openstack_blockstorage_qos_v3")) + } + + log.Printf("[DEBUG] Retrieved openstack_blockstorage_qos_v3 %s: %#v", d.Id(), qosRes) + + d.Set("region", GetRegion(d, config)) + d.Set("name", qosRes.Name) + d.Set("consumer", qosRes.Consumer) + + if err := d.Set("specs", qosRes.Specs); err != nil { + log.Printf("[WARN] Unable to set specs for openstack_blockstorage_qos_v3 %s: %s", d.Id(), err) + } + + return nil +} + +func resourceBlockStorageQosV3Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*Config) + blockStorageClient, err := config.BlockStorageV3Client(GetRegion(d, config)) + if err != nil { + return diag.Errorf("Error creating OpenStack block storage client: %s", err) + } + + hasChange := false + var updateOpts qos.UpdateOpts + + if d.HasChange("consumer") { + hasChange = true + consumer := qos.QoSConsumer(d.Get("consumer").(string)) + updateOpts.Consumer = consumer + } + + if d.HasChange("specs") { + oldSpecsRaw, newSpecsRaw := d.GetChange("specs") + + // Delete all old specs. + var deleteKeys qos.DeleteKeysOpts + for oldKey := range oldSpecsRaw.(map[string]interface{}) { + deleteKeys = append(deleteKeys, oldKey) + } + err = qos.DeleteKeys(blockStorageClient, d.Id(), deleteKeys).ExtractErr() + if err != nil { + return diag.Errorf("Error deleting specs for openstack_blockstorage_qos_v3 %s: %s", d.Id(), err) + } + + // Add new specs to UpdateOpts + newSpecs := expandToMapStringString(newSpecsRaw.(map[string]interface{})) + if len(newSpecs) > 0 { + hasChange = true + updateOpts.Specs = newSpecs + } + } + + if hasChange { + _, err = qos.Update(blockStorageClient, d.Id(), updateOpts).Extract() + if err != nil { + return diag.Errorf("Error updating openstack_blockstorage_qos_v3 %s: %s", d.Id(), err) + } + } + + return resourceBlockStorageQosV3Read(ctx, d, meta) +} + +func resourceBlockStorageQosV3Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*Config) + blockStorageClient, err := config.BlockStorageV3Client(GetRegion(d, config)) + if err != nil { + return diag.Errorf("Error creating OpenStack block storage client: %s", err) + } + + // remove all associations first + err = qos.DisassociateAll(blockStorageClient, d.Id()).ExtractErr() + if err != nil { + return diag.FromErr(CheckDeleted(d, err, "Error deleting openstack_blockstorage_qos_v3 associations")) + } + + // Delete the QoS itself + err = qos.Delete(blockStorageClient, d.Id(), qos.DeleteOpts{}).ExtractErr() + if err != nil { + return diag.FromErr(CheckDeleted(d, err, "Error deleting openstack_blockstorage_qos_v3")) + } + + return nil +} diff --git a/openstack/resource_openstack_blockstorage_qos_v3_test.go b/openstack/resource_openstack_blockstorage_qos_v3_test.go new file mode 100644 index 000000000..f654645e9 --- /dev/null +++ b/openstack/resource_openstack_blockstorage_qos_v3_test.go @@ -0,0 +1,153 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/qos" +) + +func TestAccBlockStorageQosV3_basic(t *testing.T) { + var qosTest qos.QoS + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAdminOnly(t) + }, + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckBlockStorageQosV3Destroy, + Steps: []resource.TestStep{ + { + Config: testAccBlockStorageQosV3Basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckBlockStorageQosV3Exists("openstack_blockstorage_qos_v3.qos", &qosTest), + resource.TestCheckResourceAttr( + "openstack_blockstorage_qos_v3.qos", "name", "foo"), + resource.TestCheckResourceAttr( + "openstack_blockstorage_qos_v3.qos", "consumer", "front-end"), + resource.TestCheckResourceAttr( + "openstack_blockstorage_qos_v3.qos", "specs.%", "1"), + resource.TestCheckResourceAttr( + "openstack_blockstorage_qos_v3.qos", "specs.read_iops_sec", "20000"), + ), + }, + { + Config: testAccBlockStorageQosV3Update1, + Check: resource.ComposeTestCheckFunc( + testAccCheckBlockStorageQosV3Exists("openstack_blockstorage_qos_v3.qos", &qosTest), + resource.TestCheckResourceAttr( + "openstack_blockstorage_qos_v3.qos", "name", "foo"), + resource.TestCheckResourceAttr( + "openstack_blockstorage_qos_v3.qos", "consumer", "back-end"), + resource.TestCheckResourceAttr( + "openstack_blockstorage_qos_v3.qos", "specs.%", "2"), + resource.TestCheckResourceAttr( + "openstack_blockstorage_qos_v3.qos", "specs.read_iops_sec", "40000"), + resource.TestCheckResourceAttr( + "openstack_blockstorage_qos_v3.qos", "specs.write_iops_sec", "40000"), + ), + }, + { + Config: testAccBlockStorageQosV3Update2, + Check: resource.ComposeTestCheckFunc( + testAccCheckBlockStorageQosV3Exists("openstack_blockstorage_qos_v3.qos", &qosTest), + resource.TestCheckResourceAttr( + "openstack_blockstorage_qos_v3.qos", "name", "foo"), + resource.TestCheckResourceAttr( + "openstack_blockstorage_qos_v3.qos", "consumer", "back-end"), + resource.TestCheckResourceAttr( + "openstack_blockstorage_qos_v3.qos", "specs.%", "0"), + ), + }, + }, + }) +} + +func testAccCheckBlockStorageQosV3Destroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + blockStorageClient, err := config.BlockStorageV3Client(osRegionName) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_blockstorage_qos_v3" { + continue + } + + _, err := qos.Get(blockStorageClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Qos still exists") + } + } + + return nil +} + +func testAccCheckBlockStorageQosV3Exists(n string, qosTest *qos.QoS) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + blockStorageClient, err := config.BlockStorageV3Client(osRegionName) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + found, err := qos.Get(blockStorageClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Qos not found") + } + + *qosTest = *found + + return nil + } +} + +const testAccBlockStorageQosV3Basic = ` +resource "openstack_blockstorage_qos_v3" "qos" { + name = "foo" + consumer = "front-end" + specs = { + read_iops_sec = "20000" + } + +} +` + +const testAccBlockStorageQosV3Update1 = ` +resource "openstack_blockstorage_qos_v3" "qos" { + name = "foo" + consumer = "back-end" + specs = { + read_iops_sec = "40000" + write_iops_sec = "40000" + } + +} +` + +const testAccBlockStorageQosV3Update2 = ` +resource "openstack_blockstorage_qos_v3" "qos" { + name = "foo" + consumer = "back-end" + specs = { + } +} +` diff --git a/website/docs/r/blockstorage_qos_v3.html.markdown b/website/docs/r/blockstorage_qos_v3.html.markdown new file mode 100644 index 000000000..8dda1f977 --- /dev/null +++ b/website/docs/r/blockstorage_qos_v3.html.markdown @@ -0,0 +1,61 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_blockstorage_qos_v3" +sidebar_current: "docs-openstack-resource-blockstorage-qos-v3" +description: |- + Manages a V3 Quality-Of-Servirce (qos) resource within OpenStack. +--- + +# openstack\_blockstorage\_qos\_v3 + +Manages a V3 block storage Quality-Of-Servirce (qos) resource within OpenStack. + +~> **Note:** This usually requires admin privileges. + + +## Example Usage + +```hcl +resource "openstack_blockstorage_qos_v3" "qos" { + name = "foo" + consumer = "back-end" + specs = { + read_iops_sec = "40000" + write_iops_sec = "40000" + } +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional) The region in which to create the qos. If omitted, + the `region` argument of the provider is used. Changing this creates + a new qos. + +* `name` - (Required) Name of the qos. Changing this creates a new qos. + +* `consumer` - (Optional) The consumer of qos. Can be one of `front-end`, + `back-end` or `both`. Changing this updates the `consumer` of an + existing qos. + +* `specs` - (Optional) Key/Value pairs of specs for the qos. + +## Attributes Reference + +The following attributes are exported: + +* `region` - See Argument Reference above. +* `name` - See Argument Reference above. +* `consumer` - See Argument Reference above. +* `specs` - See Argument Reference above. + +## Import + +Qos can be imported using the `qos_id`, e.g. + +``` +$ terraform import openstack_blockstorage_qos_v3.qos 941793f0-0a34-4bc4-b72e-a6326ae58283 +``` diff --git a/website/openstack.erb b/website/openstack.erb index 53cd176ea..e650d7fa6 100644 --- a/website/openstack.erb +++ b/website/openstack.erb @@ -160,6 +160,9 @@