diff --git a/docs/resources/vpc_bandwidth_associate_v2.md b/docs/resources/vpc_bandwidth_associate_v2.md new file mode 100644 index 000000000..33795fe89 --- /dev/null +++ b/docs/resources/vpc_bandwidth_associate_v2.md @@ -0,0 +1,59 @@ +--- +subcategory: "Virtual Private Cloud (VPC)" +--- + +# opentelekomcloud_vpc_bandwidth_associate_v2 + +Provides a resource to associate floating IP with a shared bandwidth within Open Telekom Cloud. + +## Example Usage + +```hcl +resource "opentelekomcloud_networking_floatingip_v2" "ip1" {} +resource "opentelekomcloud_networking_floatingip_v2" "ip2" {} + +resource "opentelekomcloud_vpc_bandwidth_v2" "band20m" { + name = "bandwidth-20MBit" + size = 20 +} + +resource "opentelekomcloud_vpc_bandwidth_associate_v2" "associate" { + bandwidth = opentelekomcloud_vpc_bandwidth_v2.band20m.id + floating_ips = [ + opentelekomcloud_networking_floatingip_v2.ip1.id, + opentelekomcloud_networking_floatingip_v2.ip2.id, + ] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `bandwidth` - (Required) Specifies ID of the bandwidth to be assigned. + +* `floating_ips` - (Required) Specifies IDs of floating IPs to be added to the bandwidth. + +-> +After an EIP is removed from a shared bandwidth, a dedicated bandwidth will be allocated to the EIP, and you will be +billed for the dedicated bandwidth. + +* `backup_charge_mode` - (Optional) Specifies whether the dedicated bandwidth used by the EIP that has been removed from + a shared bandwidth is billed by traffic or by bandwidth. + + The value can be `bandwidth` or `traffic`. + + Default value is `bandwidth`. + +* `backup_size` - (Optional) Specifies the size (Mbit/s) of the dedicated bandwidth used by the EIP that has been + removed from a shared bandwidth. + + Default value is `1`. + +## Import + +VPC bandwidth association can be imported using the bandwidth `id`, e.g. + +```sh +terraform import opentelekomcloud_vpc_bandwidth_associate_v2.associate eb187fc8-e482-43eb-a18a-9da947ef89f6 +``` diff --git a/docs/resources/vpc_bandwidth_v2.md b/docs/resources/vpc_bandwidth_v2.md index 8f89754d7..8b4996b3c 100644 --- a/docs/resources/vpc_bandwidth_v2.md +++ b/docs/resources/vpc_bandwidth_v2.md @@ -3,7 +3,7 @@ subcategory: "Virtual Private Cloud (VPC)" --- # opentelekomcloud_vpc_bandwidth_v2 -Provides a resource to create a shared bandwidth within OpenTelekomCloud. +Provides a resource to create a shared bandwidth within Open Telekom Cloud. ## Example Usage diff --git a/opentelekomcloud/acceptance/vpc/resource_opentelekomcloud_vpc_bandwidth_associate_v2_test.go b/opentelekomcloud/acceptance/vpc/resource_opentelekomcloud_vpc_bandwidth_associate_v2_test.go new file mode 100644 index 000000000..2673619f2 --- /dev/null +++ b/opentelekomcloud/acceptance/vpc/resource_opentelekomcloud_vpc_bandwidth_associate_v2_test.go @@ -0,0 +1,149 @@ +package acceptance + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/networking/v2/bandwidths" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/acceptance/common" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/acceptance/common/quotas" +) + +const resourceBandwidthAssociateName = "opentelekomcloud_vpc_bandwidth_associate_v2.associate" + +func TestBandwidthAssociateV2_basic(t *testing.T) { + var b bandwidths.Bandwidth + + t.Parallel() + qts := quotas.MultipleQuotas{ + {Q: quotas.SharedBandwidth, Count: 1}, + {Q: quotas.FloatingIP, Count: 2}, + } + quotas.BookMany(t, qts) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { common.TestAccPreCheck(t) }, + ProviderFactories: common.TestAccProviderFactories, + CheckDestroy: testCheckBandwidthV2Destroy, + Steps: []resource.TestStep{ + { + Config: testBandwidthAssociateV2Basic, + Check: resource.ComposeTestCheckFunc( + testCheckBandwidthExists(resourceBandwidthAssociateName, &b), + resource.TestCheckResourceAttr(resourceBandwidthAssociateName, "floating_ips.#", "1"), + ), + }, + { + Config: testBandwidthAssociateV2Updated, + Check: resource.ComposeTestCheckFunc( + testCheckBandwidthExists(resourceBandwidthAssociateName, &b), + resource.TestCheckResourceAttr(resourceBandwidthAssociateName, "floating_ips.#", "1"), + ), + }, + }, + }) +} + +func TestBandwidthAssociateV2_EIPv1(t *testing.T) { + var b bandwidths.Bandwidth + + t.Parallel() + qts := quotas.MultipleQuotas{ + {Q: quotas.SharedBandwidth, Count: 1}, + {Q: quotas.FloatingIP, Count: 1}, + } + quotas.BookMany(t, qts) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { common.TestAccPreCheck(t) }, + ProviderFactories: common.TestAccProviderFactories, + CheckDestroy: testCheckBandwidthV2Destroy, + Steps: []resource.TestStep{ + { + Config: testBandwidthAssociateV2EipV1, + Check: resource.ComposeTestCheckFunc( + testCheckBandwidthExists(resourceBandwidthAssociateName, &b), + resource.TestCheckResourceAttr(resourceBandwidthAssociateName, "floating_ips.#", "1"), + ), + ExpectNonEmptyPlan: true, // opentelekomcloud_vpc_eip_v1 bandwidth is updated + }, + }, + }) +} + +func TestBandwidthAssociateV2_import(t *testing.T) { + t.Parallel() + qts := quotas.MultipleQuotas{ + {Q: quotas.SharedBandwidth, Count: 1}, + {Q: quotas.FloatingIP, Count: 1}, + } + quotas.BookMany(t, qts) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { common.TestAccPreCheck(t) }, + ProviderFactories: common.TestAccProviderFactories, + CheckDestroy: testCheckBandwidthV2Destroy, + Steps: []resource.TestStep{ + { + Config: testBandwidthAssociateV2Basic, + }, + { + ResourceName: resourceBandwidthAssociateName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"backup_charge_mode", "backup_size"}, + }, + }, + }) +} + +const testBandwidthAssociateV2Basic = ` +resource "opentelekomcloud_networking_floatingip_v2" "ip" {} + +resource "opentelekomcloud_vpc_bandwidth_v2" "band_test" { + name = "shared-test-associate" + size = 20 +} + +resource "opentelekomcloud_vpc_bandwidth_associate_v2" "associate" { + bandwidth = opentelekomcloud_vpc_bandwidth_v2.band_test.id + floating_ips = [opentelekomcloud_networking_floatingip_v2.ip.id] +} +` + +const testBandwidthAssociateV2Updated = ` +resource "opentelekomcloud_networking_floatingip_v2" "ip2" {} + +resource "opentelekomcloud_vpc_bandwidth_v2" "band_test" { + name = "shared-test-associate" + size = 20 +} + +resource "opentelekomcloud_vpc_bandwidth_associate_v2" "associate" { + bandwidth = opentelekomcloud_vpc_bandwidth_v2.band_test.id + floating_ips = [opentelekomcloud_networking_floatingip_v2.ip2.id] +} +` + +const testBandwidthAssociateV2EipV1 = ` +resource "opentelekomcloud_vpc_eip_v1" "eip" { + bandwidth { + name = "tmp-band" + share_type = "PER" + size = 10 + } + publicip { + type = "5_bgp" + } +} + +resource "opentelekomcloud_vpc_bandwidth_v2" "band_test" { + name = "shared-test-associate" + size = 20 +} + +resource "opentelekomcloud_vpc_bandwidth_associate_v2" "associate" { + bandwidth = opentelekomcloud_vpc_bandwidth_v2.band_test.id + floating_ips = [opentelekomcloud_vpc_eip_v1.eip.id] +} +` diff --git a/opentelekomcloud/common/utils.go b/opentelekomcloud/common/utils.go index a5b014f01..bd10ee9cc 100644 --- a/opentelekomcloud/common/utils.go +++ b/opentelekomcloud/common/utils.go @@ -209,3 +209,11 @@ var ( DataSourceTooFewDiag = diag.Errorf("your query returned no results. Please change your search criteria and try again.") DataSourceTooManyDiag = diag.Errorf("your query returned more than one result. Please change your search criteria and try again.") ) + +// GetSetChanges returns a pair of sets describing removed and added items +func GetSetChanges(d *schema.ResourceData, key string) (removed, added *schema.Set) { + oldOne, newOne := d.GetChange(key) + oldSet := oldOne.(*schema.Set) + newSet := newOne.(*schema.Set) + return oldSet.Difference(newSet), newSet.Difference(oldSet) +} diff --git a/opentelekomcloud/provider.go b/opentelekomcloud/provider.go index 8c23162bc..62359c40a 100644 --- a/opentelekomcloud/provider.go +++ b/opentelekomcloud/provider.go @@ -424,6 +424,7 @@ func Provider() *schema.Provider { "opentelekomcloud_swr_organization_permissions_v2": swr.ResourceSwrOrganizationPermissionsV2(), "opentelekomcloud_swr_organization_v2": swr.ResourceSwrOrganizationV2(), "opentelekomcloud_swr_repository_v2": swr.ResourceSwrRepositoryV2(), + "opentelekomcloud_vpc_bandwidth_associate_v2": vpc.ResourceBandwidthAssociateV2(), "opentelekomcloud_vpc_bandwidth_v2": vpc.ResourceBandwidthV2(), "opentelekomcloud_vpc_eip_v1": vpc.ResourceVpcEIPV1(), "opentelekomcloud_vpc_v1": vpc.ResourceVirtualPrivateCloudV1(), diff --git a/opentelekomcloud/services/vpc/resource_opentelekomcloud_vpc_bandwidth_associate_v2.go b/opentelekomcloud/services/vpc/resource_opentelekomcloud_vpc_bandwidth_associate_v2.go new file mode 100644 index 000000000..a7ac572ae --- /dev/null +++ b/opentelekomcloud/services/vpc/resource_opentelekomcloud_vpc_bandwidth_associate_v2.go @@ -0,0 +1,210 @@ +package vpc + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/networking/v2/bandwidths" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/cfg" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/fmterr" +) + +func ResourceBandwidthAssociateV2() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceBandwidthAssociateV2Create, + ReadContext: resourceBandwidthAssociateV2Read, + UpdateContext: resourceBandwidthAssociateV2Update, + DeleteContext: resourceBandwidthAssociateV2Delete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "bandwidth": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "floating_ips": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "backup_charge_mode": { + Type: schema.TypeString, + Optional: true, + Default: "bandwidth", + }, + "backup_size": { + Type: schema.TypeInt, + Optional: true, + Default: 1, + }, + }, + } +} + +func resourceBandwidthAssociateV2Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) { + return config.NetworkingV2Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV2Client, err) + } + + d.SetId(d.Get("bandwidth").(string)) + + ips := d.Get("floating_ips").(*schema.Set) + if err := addIPsToBandwidth(client, d, ips); err != nil { + return diag.FromErr(err) + } + + clientCtx := common.CtxWithClient(ctx, client, keyClientV2) + return resourceBandwidthAssociateV2Read(clientCtx, d, meta) +} + +func resourceBandwidthAssociateV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) { + return config.NetworkingV2Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV2Client, err) + } + + bandwidth, err := bandwidths.Get(client, d.Id()).Extract() + if err != nil { + return diag.FromErr(common.CheckDeleted(d, err, "error getting bandwidth info")) + } + ips := make([]string, len(bandwidth.PublicIpInfo)) + for i, ipInfo := range bandwidth.PublicIpInfo { + ips[i] = ipInfo.ID + } + mErr := multierror.Append( + d.Set("bandwidth", d.Id()), + d.Set("floating_ips", ips), + ) + if err := mErr.ErrorOrNil(); err != nil { + return fmterr.Errorf("error setting bandwidth associate fields: %w", err) + } + + return nil +} + +func resourceBandwidthAssociateV2Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) { + return config.NetworkingV2Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV2Client, err) + } + + removed, added := common.GetSetChanges(d, "floating_ips") + if err := removeIPsFromBandwidth(client, d, removed); err != nil { + return diag.FromErr(err) + } + if err := addIPsToBandwidth(client, d, added); err != nil { + return diag.FromErr(err) + } + + clientCtx := common.CtxWithClient(ctx, client, keyClientV2) + return resourceBandwidthAssociateV2Read(clientCtx, d, meta) +} + +func resourceBandwidthAssociateV2Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) { + return config.NetworkingV2Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV2Client, err) + } + + ips := d.Get("floating_ips").(*schema.Set) + if err := removeIPsFromBandwidth(client, d, ips); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func addIPsToBandwidth(client *golangsdk.ServiceClient, d *schema.ResourceData, ips *schema.Set) error { + ips, err := filterExistingFloatingIPs(client, ips) + if err != nil { + return err + } + if ips.Len() == 0 { + return nil + } + + ipOpts := make([]bandwidths.PublicIpInfoInsertOpts, ips.Len()) + for i, ip := range ips.List() { + ipOpts[i] = bandwidths.PublicIpInfoInsertOpts{ + PublicIpID: ip.(string), + } + } + opts := bandwidths.InsertOpts{PublicIpInfo: ipOpts} + + if _, err := bandwidths.Insert(client, d.Id(), opts).Extract(); err != nil { + return fmt.Errorf("error adding IPs to the bandwidth: %w", err) + } + return nil +} + +func removeIPsFromBandwidth(client *golangsdk.ServiceClient, d *schema.ResourceData, ips *schema.Set) error { + ips, err := filterExistingFloatingIPs(client, ips) + if err != nil { + return err + } + if ips.Len() == 0 { + return nil + } + + ipInfo := make([]bandwidths.PublicIpInfoID, ips.Len()) + for i, ip := range ips.List() { + ipInfo[i] = bandwidths.PublicIpInfoID{ + PublicIpID: ip.(string), + } + } + + opts := bandwidths.RemoveOpts{ + ChargeMode: d.Get("backup_charge_mode").(string), + Size: d.Get("backup_size").(int), + PublicIpInfo: ipInfo, + } + + if err := bandwidths.Remove(client, d.Id(), opts).ExtractErr(); err != nil { + return fmt.Errorf("error removing IPs from the bandwidth: %w", err) + } + return nil +} + +// filterExistingFloatingIPs returns only existing IPs from given slice +func filterExistingFloatingIPs(clientV2 *golangsdk.ServiceClient, ipIDs *schema.Set) (*schema.Set, error) { + filtered := schema.NewSet(schema.HashString, []interface{}{}) + + // check IPs in v2: + pages, err := floatingips.List(clientV2, floatingips.ListOpts{}).AllPages() + if err != nil { + return nil, fmt.Errorf("error listing floating IPs: %w", err) + } + fips, err := floatingips.ExtractFloatingIPs(pages) + if err != nil { + return nil, fmt.Errorf("error extracting floating IPs: %w", err) + } + for _, ip := range fips { + if id := ip.ID; ipIDs.Contains(id) { + filtered.Add(id) + } + } + return filtered, nil +} diff --git a/releasenotes/notes/vpc-bandwidth-associate-v2-b04581b16cdb858c.yaml b/releasenotes/notes/vpc-bandwidth-associate-v2-b04581b16cdb858c.yaml new file mode 100644 index 000000000..b40c722e8 --- /dev/null +++ b/releasenotes/notes/vpc-bandwidth-associate-v2-b04581b16cdb858c.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + **New Resource:** ``opentelekomcloud_vpc_bandwidth_associate_v2`` (`#1549 `_) diff --git a/releasenotes/notes/vpc-bandwidth-v2-b34b0a3322be891a.yaml b/releasenotes/notes/vpc-bandwidth-v2-b34b0a3322be891a.yaml index 90e2e5d81..a96e7294f 100644 --- a/releasenotes/notes/vpc-bandwidth-v2-b34b0a3322be891a.yaml +++ b/releasenotes/notes/vpc-bandwidth-v2-b34b0a3322be891a.yaml @@ -1,4 +1,4 @@ --- features: - | - **New Resource:** ``opentelekomcloud_vpc_bandwidth_v2`` + **New Resource:** ``opentelekomcloud_vpc_bandwidth_v2`` (`#1548 `_)