From 4291415f272f110b796af4615f7837b922c5ed9e Mon Sep 17 00:00:00 2001 From: Zhenguo Niu Date: Thu, 20 May 2021 12:25:05 +0000 Subject: [PATCH] Add ELB v3 pools and members --- docs/resources/elb_member.md | 57 +++ docs/resources/elb_pool.md | 79 ++++ go.mod | 2 +- go.sum | 4 +- huaweicloud/provider.go | 2 + .../resource_huaweicloud_elb_member.go | 174 +++++++++ .../resource_huaweicloud_elb_member_test.go | 231 ++++++++++++ huaweicloud/resource_huaweicloud_elb_pool.go | 296 +++++++++++++++ .../resource_huaweicloud_elb_pool_test.go | 189 ++++++++++ .../openstack/elb/v3/pools/requests.go | 338 ++++++++++++++++++ .../openstack/elb/v3/pools/results.go | 271 ++++++++++++++ .../golangsdk/openstack/elb/v3/pools/urls.go | 25 ++ .../ims/v2/cloudimages/results_job.go | 3 +- vendor/modules.txt | 3 +- 14 files changed, 1669 insertions(+), 5 deletions(-) create mode 100644 docs/resources/elb_member.md create mode 100644 docs/resources/elb_pool.md create mode 100644 huaweicloud/resource_huaweicloud_elb_member.go create mode 100644 huaweicloud/resource_huaweicloud_elb_member_test.go create mode 100644 huaweicloud/resource_huaweicloud_elb_pool.go create mode 100644 huaweicloud/resource_huaweicloud_elb_pool_test.go create mode 100644 vendor/github.com/huaweicloud/golangsdk/openstack/elb/v3/pools/requests.go create mode 100644 vendor/github.com/huaweicloud/golangsdk/openstack/elb/v3/pools/results.go create mode 100644 vendor/github.com/huaweicloud/golangsdk/openstack/elb/v3/pools/urls.go diff --git a/docs/resources/elb_member.md b/docs/resources/elb_member.md new file mode 100644 index 0000000000..24637eec8f --- /dev/null +++ b/docs/resources/elb_member.md @@ -0,0 +1,57 @@ +--- +subcategory: "Dedicated Load Balance (Dedicated ELB)" +--- + +# huaweicloud\_elb\_member + +Manages an ELB member resource within HuaweiCloud. + +## Example Usage + +```hcl +resource "huaweicloud_elb_member" "member_1" { + address = "192.168.199.23" + protocol_port = 8080 + pool_id = var.pool_id + subnet_id = var.subnet_id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) The region in which to create the ELB member resource. + If omitted, the the provider-level region will be used. + Changing this creates a new member. + +* `pool_id` - (Required, String, ForceNew) The id of the pool that this member will be + assigned to. + +* `subnet_id` - (Required, String, ForceNew) The subnet in which to access the member + +* `name` - (Optional, String) Human-readable name for the member. + +* `address` - (Required, String, ForceNew) The IP address of the member to receive traffic from + the load balancer. Changing this creates a new member. + +* `protocol_port` - (Required, Int, ForceNew) The port on which to listen for client traffic. + Changing this creates a new member. + +* `weight` - (Optional, Int) A positive integer value that indicates the relative + portion of traffic that this member should receive from the pool. For + example, a member with a weight of 10 receives five times as much traffic + as a member with a weight of 2. + + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The unique ID for the member. + +## Timeouts +This resource provides the following timeouts configuration options: +- `create` - Default is 10 minute. +- `update` - Default is 10 minute. +- `delete` - Default is 10 minute. diff --git a/docs/resources/elb_pool.md b/docs/resources/elb_pool.md new file mode 100644 index 0000000000..6502ed9368 --- /dev/null +++ b/docs/resources/elb_pool.md @@ -0,0 +1,79 @@ +--- +subcategory: "Dedicated Load Balance (Dedicated ELB)" +--- + +# huaweicloud\_elb\_pool + +Manages an ELB pool resource within HuaweiCloud. + +## Example Usage + +```hcl +resource "huaweicloud_elb_pool" "pool_1" { + protocol = "HTTP" + lb_method = "ROUND_ROBIN" + listener_id = "{{ listener_id }}" + + persistence { + type = "HTTP_COOKIE" + cookie_name = "testCookie" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) The region in which to create the ELB pool resource. + If omitted, the the provider-level region will be used. + Changing this creates a new pool. + +* `name` - (Optional, String) Human-readable name for the pool. + +* `description` - (Optional, String) Human-readable description for the pool. + +* `protocol` - (Required, String, ForceNew) The protocol - can either be TCP, UDP or HTTP. + + - When the protocol used by the listener is UDP, the protocol of the backend pool must be UDP. + - When the protocol used by the listener is TCP, the protocol of the backend pool must be TCP. + - When the protocol used by the listener is HTTP or TERMINATED_HTTPS, the protocol of the backend pool must be HTTP. + + Changing this creates a new pool. + +* `loadbalancer_id` - (Optional, String, ForceNew) The load balancer on which to provision this + pool. Changing this creates a new pool. + Note: One of LoadbalancerID or ListenerID must be provided. + +* `listener_id` - (Optional, String, ForceNew) The Listener on which the members of the pool + will be associated with. Changing this creates a new pool. + Note: One of LoadbalancerID or ListenerID must be provided. + +* `lb_method` - (Required, String) The load balancing algorithm to + distribute traffic to the pool's members. Must be one of + ROUND_ROBIN, LEAST_CONNECTIONS, or SOURCE_IP. + +* `persistence` - (Optional, List, ForceNew) Omit this field to prevent session persistence. Indicates + whether connections in the same session will be processed by the same Pool + member or not. Changing this creates a new pool. + +The `persistence` argument supports: + +* `type` - (Required, String, ForceNew) The type of persistence mode. The current specification + supports SOURCE_IP, HTTP_COOKIE, and APP_COOKIE. + +* `cookie_name` - (Optional, String, ForceNew) The name of the cookie if persistence mode is set + appropriately. Required if `type = APP_COOKIE`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The unique ID for the pool. + +## Timeouts +This resource provides the following timeouts configuration options: +- `create` - Default is 10 minute. +- `update` - Default is 10 minute. +- `delete` - Default is 10 minute. + diff --git a/go.mod b/go.mod index 4bb777048e..fb62e579d9 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/hashicorp/errwrap v1.0.0 github.com/hashicorp/go-multierror v1.0.0 github.com/hashicorp/terraform-plugin-sdk v1.16.0 - github.com/huaweicloud/golangsdk v0.0.0-20210519120215-6602b8a10c02 + github.com/huaweicloud/golangsdk v0.0.0-20210521045610-c7bf719c8e93 github.com/jen20/awspolicyequivalence v1.1.0 github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa // indirect github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index 0c905332c2..89bb654e4d 100644 --- a/go.sum +++ b/go.sum @@ -206,8 +206,8 @@ github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596/go.mod github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/huaweicloud/golangsdk v0.0.0-20210519120215-6602b8a10c02 h1:t3VcYe8eUxdRqk0c3PQd50DtOPCEOiNv9LHi9YTNQ5I= -github.com/huaweicloud/golangsdk v0.0.0-20210519120215-6602b8a10c02/go.mod h1:fcOI5u+0f62JtJd7zkCch/Z57BNC6bhqb32TKuiF4r0= +github.com/huaweicloud/golangsdk v0.0.0-20210521045610-c7bf719c8e93 h1:xHX5P34vk5p0Ihp2jZkD2S5KrZIBIaauluMtBvPBeU8= +github.com/huaweicloud/golangsdk v0.0.0-20210521045610-c7bf719c8e93/go.mod h1:fcOI5u+0f62JtJd7zkCch/Z57BNC6bhqb32TKuiF4r0= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go index 7812304ceb..acff427d77 100644 --- a/huaweicloud/provider.go +++ b/huaweicloud/provider.go @@ -399,6 +399,8 @@ func Provider() terraform.ResourceProvider { "huaweicloud_elb_listener": ResourceListenerV3(), "huaweicloud_elb_loadbalancer": ResourceLoadBalancerV3(), "huaweicloud_elb_ipgroup": ResourceIpGroupV3(), + "huaweicloud_elb_pool": ResourcePoolV3(), + "huaweicloud_elb_member": ResourceMemberV3(), "huaweicloud_evs_snapshot": ResourceEvsSnapshotV2(), "huaweicloud_evs_volume": ResourceEvsStorageVolumeV3(), "huaweicloud_fgs_function": resourceFgsFunctionV2(), diff --git a/huaweicloud/resource_huaweicloud_elb_member.go b/huaweicloud/resource_huaweicloud_elb_member.go new file mode 100644 index 0000000000..08ba799362 --- /dev/null +++ b/huaweicloud/resource_huaweicloud_elb_member.go @@ -0,0 +1,174 @@ +package huaweicloud + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + + "github.com/huaweicloud/golangsdk/openstack/elb/v3/pools" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" +) + +func ResourceMemberV3() *schema.Resource { + return &schema.Resource{ + Create: resourceMemberV3Create, + Read: resourceMemberV3Read, + Update: resourceMemberV3Update, + Delete: resourceMemberV3Delete, + + 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, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + }, + + "address": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "protocol_port": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "weight": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(int) + if value < 1 { + errors = append(errors, fmt.Errorf( + "Only numbers greater than 0 are supported values for 'weight'")) + } + return + }, + }, + + "subnet_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "pool_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceMemberV3Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + elbClient, err := config.ElbV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud elb client: %s", err) + } + + createOpts := pools.CreateMemberOpts{ + Name: d.Get("name").(string), + Address: d.Get("address").(string), + ProtocolPort: d.Get("protocol_port").(int), + Weight: d.Get("weight").(int), + } + + // Must omit if not set + if v, ok := d.GetOk("subnet_id"); ok { + createOpts.SubnetID = v.(string) + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + poolID := d.Get("pool_id").(string) + member, err := pools.CreateMember(elbClient, poolID, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating member: %s", err) + } + + d.SetId(member.ID) + + return resourceMemberV3Read(d, meta) +} + +func resourceMemberV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + elbClient, err := config.ElbV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud elb client: %s", err) + } + + member, err := pools.GetMember(elbClient, d.Get("pool_id").(string), d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "member") + } + + log.Printf("[DEBUG] Retrieved member %s: %#v", d.Id(), member) + + d.Set("name", member.Name) + d.Set("weight", member.Weight) + d.Set("subnet_id", member.SubnetID) + d.Set("address", member.Address) + d.Set("protocol_port", member.ProtocolPort) + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceMemberV3Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + elbClient, err := config.ElbV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud elb client: %s", err) + } + + var updateOpts pools.UpdateMemberOpts + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("weight") { + updateOpts.Weight = d.Get("weight").(int) + } + + log.Printf("[DEBUG] Updating member %s with options: %#v", d.Id(), updateOpts) + poolID := d.Get("pool_id").(string) + _, err = pools.UpdateMember(elbClient, poolID, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Unable to update member %s: %s", d.Id(), err) + } + + return resourceMemberV3Read(d, meta) +} + +func resourceMemberV3Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + elbClient, err := config.ElbV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud elb client: %s", err) + } + + poolID := d.Get("pool_id").(string) + err = pools.DeleteMember(elbClient, poolID, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Unable to delete member %s: %s", d.Id(), err) + } + return nil +} diff --git a/huaweicloud/resource_huaweicloud_elb_member_test.go b/huaweicloud/resource_huaweicloud_elb_member_test.go new file mode 100644 index 0000000000..9aa3cef856 --- /dev/null +++ b/huaweicloud/resource_huaweicloud_elb_member_test.go @@ -0,0 +1,231 @@ +package huaweicloud + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/huaweicloud/golangsdk/openstack/elb/v3/pools" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" +) + +func TestAccElbV3Member_basic(t *testing.T) { + var member_1 pools.Member + var member_2 pools.Member + rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckElbV3MemberDestroy, + Steps: []resource.TestStep{ + { + Config: testAccElbV3MemberConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckElbV3MemberExists("huaweicloud_elb_member.member_1", &member_1), + testAccCheckElbV3MemberExists("huaweicloud_elb_member.member_2", &member_2), + ), + }, + { + Config: testAccElbV3MemberConfig_update(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("huaweicloud_elb_member.member_1", "weight", "10"), + resource.TestCheckResourceAttr("huaweicloud_elb_member.member_2", "weight", "15"), + ), + }, + }, + }) +} + +func testAccCheckElbV3MemberDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*config.Config) + elbClient, err := config.ElbV3Client(HW_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud elb client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "huaweicloud_elb_member" { + continue + } + + poolId := rs.Primary.Attributes["pool_id"] + _, err := pools.GetMember(elbClient, poolId, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Member still exists: %s", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckElbV3MemberExists(n string, member *pools.Member) 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.Config) + elbClient, err := config.ElbV3Client(HW_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud elb client: %s", err) + } + + poolId := rs.Primary.Attributes["pool_id"] + found, err := pools.GetMember(elbClient, poolId, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Member not found") + } + + *member = *found + + return nil + } +} + +func testAccElbV3MemberConfig_basic(rName string) string { + return fmt.Sprintf(` +data "huaweicloud_vpc_subnet" "test" { + name = "subnet-default" +} + +data "huaweicloud_availability_zones" "test" {} + +resource "huaweicloud_elb_loadbalancer" "test" { + name = "%s" + ipv4_subnet_id = data.huaweicloud_vpc_subnet.test.subnet_id + ipv6_network_id = data.huaweicloud_vpc_subnet.test.id + + availability_zone = [ + data.huaweicloud_availability_zones.test.names[0] + ] +} + +resource "huaweicloud_elb_listener" "test" { + name = "%s" + description = "test description" + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = huaweicloud_elb_loadbalancer.test.id + + forward_eip = true + + idle_timeout = 60 + request_timeout = 60 + response_timeout = 60 +} + +resource "huaweicloud_elb_pool" "test" { + name = "%s" + protocol = "HTTP" + lb_method = "ROUND_ROBIN" + listener_id = huaweicloud_elb_listener.test.id +} + +resource "huaweicloud_elb_member" "member_1" { + address = "192.168.0.10" + protocol_port = 8080 + pool_id = huaweicloud_elb_pool.test.id + subnet_id = data.huaweicloud_vpc_subnet.test.subnet_id + + timeouts { + create = "5m" + update = "5m" + delete = "5m" + } +} + +resource "huaweicloud_elb_member" "member_2" { + address = "192.168.0.11" + protocol_port = 8080 + pool_id = huaweicloud_elb_pool.test.id + subnet_id = data.huaweicloud_vpc_subnet.test.subnet_id + + timeouts { + create = "5m" + update = "5m" + delete = "5m" + } +} +`, rName, rName, rName) +} + +func testAccElbV3MemberConfig_update(rName string) string { + return fmt.Sprintf(` +data "huaweicloud_vpc_subnet" "test" { + name = "subnet-default" +} + +data "huaweicloud_availability_zones" "test" {} + +resource "huaweicloud_elb_loadbalancer" "test" { + name = "%s" + ipv4_subnet_id = data.huaweicloud_vpc_subnet.test.subnet_id + ipv6_network_id = data.huaweicloud_vpc_subnet.test.id + + availability_zone = [ + data.huaweicloud_availability_zones.test.names[0] + ] +} + +resource "huaweicloud_elb_listener" "test" { + name = "%s" + description = "test description" + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = huaweicloud_elb_loadbalancer.test.id + + forward_eip = true + + idle_timeout = 60 + request_timeout = 60 + response_timeout = 60 +} + +resource "huaweicloud_elb_pool" "test" { + name = "%s" + protocol = "HTTP" + lb_method = "ROUND_ROBIN" + listener_id = huaweicloud_elb_listener.test.id +} + +resource "huaweicloud_elb_member" "member_1" { + address = "192.168.0.10" + protocol_port = 8080 + weight = 10 + pool_id = huaweicloud_elb_pool.test.id + subnet_id = data.huaweicloud_vpc_subnet.test.subnet_id + + timeouts { + create = "5m" + update = "5m" + delete = "5m" + } +} + +resource "huaweicloud_elb_member" "member_2" { + address = "192.168.0.11" + protocol_port = 8080 + weight = 15 + pool_id = huaweicloud_elb_pool.test.id + subnet_id = data.huaweicloud_vpc_subnet.test.subnet_id + + timeouts { + create = "5m" + update = "5m" + delete = "5m" + } +} +`, rName, rName, rName) +} diff --git a/huaweicloud/resource_huaweicloud_elb_pool.go b/huaweicloud/resource_huaweicloud_elb_pool.go new file mode 100644 index 0000000000..09ca60a3ec --- /dev/null +++ b/huaweicloud/resource_huaweicloud_elb_pool.go @@ -0,0 +1,296 @@ +package huaweicloud + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/openstack/elb/v3/pools" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" +) + +func ResourcePoolV3() *schema.Resource { + return &schema.Resource{ + Create: resourcePoolV3Create, + Read: resourcePoolV3Read, + Update: resourcePoolV3Update, + Delete: resourcePoolV3Delete, + + 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, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "protocol": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "TCP", "UDP", "HTTP", + }, false), + }, + + // One of loadbalancer_id or listener_id must be provided + "loadbalancer_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + AtLeastOneOf: []string{"loadbalancer_id", "listener_id"}, + }, + + // One of loadbalancer_id or listener_id must be provided + "listener_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + AtLeastOneOf: []string{"loadbalancer_id", "listener_id"}, + }, + + "lb_method": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "ROUND_ROBIN", "LEAST_CONNECTIONS", "SOURCE_IP", "QUIC_CID", + }, false), + }, + + "persistence": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "SOURCE_IP", "HTTP_COOKIE", "APP_COOKIE", + }, false), + }, + + "cookie_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + }, + } +} + +func resourcePoolV3Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + elbClient, err := config.ElbV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud elb client: %s", err) + } + + var persistence pools.SessionPersistence + if p, ok := d.GetOk("persistence"); ok { + pV := (p.([]interface{}))[0].(map[string]interface{}) + + persistence = pools.SessionPersistence{ + Type: pV["type"].(string), + } + + if persistence.Type == "APP_COOKIE" { + if pV["cookie_name"].(string) == "" { + return fmt.Errorf( + "Persistence cookie_name needs to be set if using 'APP_COOKIE' persistence type") + } + persistence.CookieName = pV["cookie_name"].(string) + } else { + if pV["cookie_name"].(string) != "" { + return fmt.Errorf( + "Persistence cookie_name can only be set if using 'APP_COOKIE' persistence type") + } + } + } + + createOpts := pools.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + Protocol: d.Get("protocol").(string), + LoadbalancerID: d.Get("loadbalancer_id").(string), + ListenerID: d.Get("listener_id").(string), + LBMethod: d.Get("lb_method").(string), + } + + // Must omit if not set + if persistence != (pools.SessionPersistence{}) { + createOpts.Persistence = &persistence + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + pool, err := pools.Create(elbClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating pool: %s", err) + } + + d.SetId(pool.ID) + + timeout := d.Timeout(schema.TimeoutCreate) + err = waitForElbV3Pool(elbClient, d.Id(), "ACTIVE", nil, timeout) + if err != nil { + return err + } + + return resourcePoolV3Read(d, meta) +} + +func resourcePoolV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + elbClient, err := config.ElbV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud elb client: %s", err) + } + + pool, err := pools.Get(elbClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "pool") + } + + log.Printf("[DEBUG] Retrieved pool %s: %#v", d.Id(), pool) + + d.Set("lb_method", pool.LBMethod) + d.Set("protocol", pool.Protocol) + d.Set("description", pool.Description) + d.Set("name", pool.Name) + d.Set("region", GetRegion(d, config)) + + if pool.Persistence.Type != "" { + var persistence []map[string]interface{} = make([]map[string]interface{}, 1) + params := make(map[string]interface{}) + params["cookie_name"] = pool.Persistence.CookieName + params["type"] = pool.Persistence.Type + persistence[0] = params + if err = d.Set("persistence", persistence); err != nil { + return fmt.Errorf("Load balance persistence set error: %s", err) + } + } + + return nil +} + +func resourcePoolV3Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + elbClient, err := config.ElbV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud elb client: %s", err) + } + + var updateOpts pools.UpdateOpts + if d.HasChange("lb_method") { + updateOpts.LBMethod = d.Get("lb_method").(string) + } + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("description") { + updateOpts.Description = d.Get("description").(string) + } + + log.Printf("[DEBUG] Updating pool %s with options: %#v", d.Id(), updateOpts) + _, err = pools.Update(elbClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Unable to update pool %s: %s", d.Id(), err) + } + + timeout := d.Timeout(schema.TimeoutUpdate) + err = waitForElbV3Pool(elbClient, d.Id(), "ACTIVE", nil, timeout) + if err != nil { + return err + } + + return resourcePoolV3Read(d, meta) +} + +func resourcePoolV3Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + elbClient, err := config.ElbV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud elb client: %s", err) + } + + log.Printf("[DEBUG] Attempting to delete pool %s", d.Id()) + err = pools.Delete(elbClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Unable to delete pool %s: %s", d.Id(), err) + } + + // Wait for Pool to delete + timeout := d.Timeout(schema.TimeoutDelete) + err = waitForElbV3Pool(elbClient, d.Id(), "DELETED", nil, timeout) + if err != nil { + return err + } + + return nil +} + +func waitForElbV3Pool(elbClient *golangsdk.ServiceClient, id string, target string, pending []string, timeout time.Duration) error { + log.Printf("[DEBUG] Waiting for pool %s to become %s.", id, target) + + stateConf := &resource.StateChangeConf{ + Target: []string{target}, + Pending: pending, + Refresh: resourceElbV3PoolRefreshFunc(elbClient, id), + Timeout: timeout, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err := stateConf.WaitForState() + if err != nil { + if _, ok := err.(golangsdk.ErrDefault404); ok { + switch target { + case "DELETED": + return nil + default: + return fmt.Errorf("Error: pool %s not found: %s", id, err) + } + } + return fmt.Errorf("Error waiting for pool %s to become %s: %s", id, target, err) + } + + return nil +} + +func resourceElbV3PoolRefreshFunc(elbClient *golangsdk.ServiceClient, poolID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + pool, err := pools.Get(elbClient, poolID).Extract() + if err != nil { + return nil, "", err + } + + // The pool resource has no Status attribute, so a successful Get is the best we can do + return pool, "ACTIVE", nil + } +} diff --git a/huaweicloud/resource_huaweicloud_elb_pool_test.go b/huaweicloud/resource_huaweicloud_elb_pool_test.go new file mode 100644 index 0000000000..573aff8dbb --- /dev/null +++ b/huaweicloud/resource_huaweicloud_elb_pool_test.go @@ -0,0 +1,189 @@ +package huaweicloud + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/huaweicloud/golangsdk/openstack/elb/v3/pools" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" +) + +func TestAccElbV3Pool_basic(t *testing.T) { + var pool pools.Pool + rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + rNameUpdate := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + resourceName := "huaweicloud_elb_pool.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckElbV3PoolDestroy, + Steps: []resource.TestStep{ + { + Config: testAccElbV3PoolConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckElbV3PoolExists(resourceName, &pool), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "lb_method", "ROUND_ROBIN"), + ), + }, + { + Config: testAccElbV3PoolConfig_update(rName, rNameUpdate), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", rNameUpdate), + resource.TestCheckResourceAttr(resourceName, "lb_method", "LEAST_CONNECTIONS"), + ), + }, + }, + }) +} + +func testAccCheckElbV3PoolDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*config.Config) + elbClient, err := config.ElbV3Client(HW_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud elb client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "huaweicloud_elb_pool" { + continue + } + + _, err := pools.Get(elbClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Pool still exists: %s", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckElbV3PoolExists(n string, pool *pools.Pool) 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.Config) + elbClient, err := config.ElbV3Client(HW_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud elb client: %s", err) + } + + found, err := pools.Get(elbClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Member not found") + } + + *pool = *found + + return nil + } +} + +func testAccElbV3PoolConfig_basic(rName string) string { + return fmt.Sprintf(` +data "huaweicloud_vpc_subnet" "test" { + name = "subnet-default" +} + +data "huaweicloud_availability_zones" "test" {} + +resource "huaweicloud_elb_loadbalancer" "test" { + name = "%s" + ipv4_subnet_id = data.huaweicloud_vpc_subnet.test.subnet_id + ipv6_network_id = data.huaweicloud_vpc_subnet.test.id + + availability_zone = [ + data.huaweicloud_availability_zones.test.names[0] + ] +} + +resource "huaweicloud_elb_listener" "test" { + name = "%s" + description = "test description" + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = huaweicloud_elb_loadbalancer.test.id + + forward_eip = true + + idle_timeout = 60 + request_timeout = 60 + response_timeout = 60 +} + +resource "huaweicloud_elb_pool" "test" { + name = "%s" + protocol = "HTTP" + lb_method = "ROUND_ROBIN" + listener_id = huaweicloud_elb_listener.test.id + + timeouts { + create = "5m" + update = "5m" + delete = "5m" + } +} +`, rName, rName, rName) +} + +func testAccElbV3PoolConfig_update(rName, rNameUpdate string) string { + return fmt.Sprintf(` +data "huaweicloud_vpc_subnet" "test" { + name = "subnet-default" +} + +data "huaweicloud_availability_zones" "test" {} + +resource "huaweicloud_elb_loadbalancer" "test" { + name = "%s" + ipv4_subnet_id = data.huaweicloud_vpc_subnet.test.subnet_id + ipv6_network_id = data.huaweicloud_vpc_subnet.test.id + + availability_zone = [ + data.huaweicloud_availability_zones.test.names[0] + ] +} + +resource "huaweicloud_elb_listener" "test" { + name = "%s" + description = "test description" + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = huaweicloud_elb_loadbalancer.test.id + + forward_eip = true + + idle_timeout = 60 + request_timeout = 60 + response_timeout = 60 +} + +resource "huaweicloud_elb_pool" "test" { + name = "%s" + protocol = "HTTP" + lb_method = "LEAST_CONNECTIONS" + listener_id = huaweicloud_elb_listener.test.id + + timeouts { + create = "5m" + update = "5m" + delete = "5m" + } +} +`, rName, rName, rNameUpdate) +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/elb/v3/pools/requests.go b/vendor/github.com/huaweicloud/golangsdk/openstack/elb/v3/pools/requests.go new file mode 100644 index 0000000000..b0a445ed3d --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/elb/v3/pools/requests.go @@ -0,0 +1,338 @@ +package pools + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToPoolListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the Pool attributes you want to see returned. SortKey allows you to +// sort by a particular Pool attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + LBMethod string `q:"lb_algorithm"` + Protocol string `q:"protocol"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + AdminStateUp *bool `q:"admin_state_up"` + Name string `q:"name"` + ID string `q:"id"` + LoadbalancerID string `q:"loadbalancer_id"` + ListenerID string `q:"listener_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToPoolListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToPoolListQuery() (string, error) { + q, err := golangsdk.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// pools. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those pools that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func List(c *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToPoolListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return PoolPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToPoolCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts struct { + // The algorithm used to distribute load between the members of the pool. The + // current specification supports LBMethodRoundRobin, LBMethodLeastConnections + // and LBMethodSourceIp as valid values for this attribute. + LBMethod string `json:"lb_algorithm" required:"true"` + + // The protocol used by the pool members, you can use either + // ProtocolTCP, ProtocolHTTP, or ProtocolHTTPS. + Protocol string `json:"protocol" required:"true"` + + // The Loadbalancer on which the members of the pool will be associated with. + // Note: one of LoadbalancerID or ListenerID must be provided. + LoadbalancerID string `json:"loadbalancer_id,omitempty" xor:"ListenerID"` + + // The Listener on which the members of the pool will be associated with. + // Note: one of LoadbalancerID or ListenerID must be provided. + ListenerID string `json:"listener_id,omitempty" xor:"LoadbalancerID"` + + // ProjectID is the UUID of the project who owns the Pool. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // Name of the pool. + Name string `json:"name,omitempty"` + + // Human-readable description for the pool. + Description string `json:"description,omitempty"` + + // Persistence is the session persistence of the pool. + // Omit this field to prevent session persistence. + Persistence *SessionPersistence `json:"session_persistence,omitempty"` + + // The administrative state of the Pool. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToPoolCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToPoolCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "pool") +} + +// Create accepts a CreateOpts struct and uses the values to create a new +// load balancer pool. +func Create(c *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToPoolCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// Get retrieves a particular pool based on its unique ID. +func Get(c *golangsdk.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToPoolUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is the common options struct used in this package's Update +// operation. +type UpdateOpts struct { + // Name of the pool. + Name string `json:"name,omitempty"` + + // Human-readable description for the pool. + Description string `json:"description,omitempty"` + + // The algorithm used to distribute load between the members of the pool. The + // current specification supports LBMethodRoundRobin, LBMethodLeastConnections + // and LBMethodSourceIp as valid values for this attribute. + LBMethod string `json:"lb_algorithm,omitempty"` + + // Persistence is the session persistence of the pool. + // Omit this field to prevent session persistence. + Persistence *SessionPersistence `json:"session_persistence,omitempty"` + + // The administrative state of the Pool. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToPoolUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToPoolUpdateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "pool") +} + +// Update allows pools to be updated. +func Update(c *golangsdk.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToPoolUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete will permanently delete a particular pool based on its unique ID. +func Delete(c *golangsdk.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// ListMemberOptsBuilder allows extensions to add additional parameters to the +// ListMembers request. +type ListMembersOptsBuilder interface { + ToMembersListQuery() (string, error) +} + +// ListMembersOpts allows the filtering and sorting of paginated collections +// through the API. Filtering is achieved by passing in struct field values +// that map to the Member attributes you want to see returned. SortKey allows +// you to sort by a particular Member attribute. SortDir sets the direction, +// and is either `asc' or `desc'. Marker and Limit are used for pagination. +type ListMembersOpts struct { + Name string `q:"name"` + Weight int `q:"weight"` + AdminStateUp *bool `q:"admin_state_up"` + TenantID string `q:"tenant_id"` + Address string `q:"address"` + ProtocolPort int `q:"protocol_port"` + ID string `q:"id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToMemberListQuery formats a ListOpts into a query string. +func (opts ListMembersOpts) ToMembersListQuery() (string, error) { + q, err := golangsdk.BuildQueryString(opts) + return q.String(), err +} + +// ListMembers returns a Pager which allows you to iterate over a collection of +// members. It accepts a ListMembersOptsBuilder, which allows you to filter and +// sort the returned collection for greater efficiency. +// +// Default policy settings return only those members that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func ListMembers(c *golangsdk.ServiceClient, poolID string, opts ListMembersOptsBuilder) pagination.Pager { + url := memberRootURL(c, poolID) + if opts != nil { + query, err := opts.ToMembersListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return MemberPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateMemberOptsBuilder allows extensions to add additional parameters to the +// CreateMember request. +type CreateMemberOptsBuilder interface { + ToMemberCreateMap() (map[string]interface{}, error) +} + +// CreateMemberOpts is the common options struct used in this package's CreateMember +// operation. +type CreateMemberOpts struct { + // The IP address of the member to receive traffic from the load balancer. + Address string `json:"address" required:"true"` + + // The port on which to listen for client traffic. + ProtocolPort int `json:"protocol_port" required:"true"` + + // Name of the Member. + Name string `json:"name,omitempty"` + + // ProjectID is the UUID of the project who owns the Member. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // A positive integer value that indicates the relative portion of traffic + // that this member should receive from the pool. For example, a member with + // a weight of 10 receives five times as much traffic as a member with a + // weight of 2. + Weight int `json:"weight,omitempty"` + + // If you omit this parameter, LBaaS uses the vip_subnet_id parameter value + // for the subnet UUID. + SubnetID string `json:"subnet_cidr_id,omitempty"` + + // The administrative state of the Pool. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToMemberCreateMap builds a request body from CreateMemberOpts. +func (opts CreateMemberOpts) ToMemberCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "member") +} + +// CreateMember will create and associate a Member with a particular Pool. +func CreateMember(c *golangsdk.ServiceClient, poolID string, opts CreateMemberOpts) (r CreateMemberResult) { + b, err := opts.ToMemberCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(memberRootURL(c, poolID), b, &r.Body, nil) + return +} + +// GetMember retrieves a particular Pool Member based on its unique ID. +func GetMember(c *golangsdk.ServiceClient, poolID string, memberID string) (r GetMemberResult) { + _, r.Err = c.Get(memberResourceURL(c, poolID, memberID), &r.Body, nil) + return +} + +// UpdateMemberOptsBuilder allows extensions to add additional parameters to the +// List request. +type UpdateMemberOptsBuilder interface { + ToMemberUpdateMap() (map[string]interface{}, error) +} + +// UpdateMemberOpts is the common options struct used in this package's Update +// operation. +type UpdateMemberOpts struct { + // Name of the Member. + Name string `json:"name,omitempty"` + + // A positive integer value that indicates the relative portion of traffic + // that this member should receive from the pool. For example, a member with + // a weight of 10 receives five times as much traffic as a member with a + // weight of 2. + Weight int `json:"weight,omitempty"` + + // The administrative state of the Pool. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToMemberUpdateMap builds a request body from UpdateMemberOpts. +func (opts UpdateMemberOpts) ToMemberUpdateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "member") +} + +// Update allows Member to be updated. +func UpdateMember(c *golangsdk.ServiceClient, poolID string, memberID string, opts UpdateMemberOptsBuilder) (r UpdateMemberResult) { + b, err := opts.ToMemberUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(memberResourceURL(c, poolID, memberID), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + return +} + +// DisassociateMember will remove and disassociate a Member from a particular +// Pool. +func DeleteMember(c *golangsdk.ServiceClient, poolID string, memberID string) (r DeleteMemberResult) { + _, r.Err = c.Delete(memberResourceURL(c, poolID, memberID), nil) + return +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/elb/v3/pools/results.go b/vendor/github.com/huaweicloud/golangsdk/openstack/elb/v3/pools/results.go new file mode 100644 index 0000000000..3d6aeb3a0a --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/elb/v3/pools/results.go @@ -0,0 +1,271 @@ +package pools + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// SessionPersistence represents the session persistence feature of the load +// balancing service. It attempts to force connections or requests in the same +// session to be processed by the same member as long as it is ative. Three +// types of persistence are supported: +// +// SOURCE_IP: With this mode, all connections originating from the same source +// IP address, will be handled by the same Member of the Pool. +// HTTP_COOKIE: With this persistence mode, the load balancing function will +// create a cookie on the first request from a client. Subsequent +// requests containing the same cookie value will be handled by +// the same Member of the Pool. +// APP_COOKIE: With this persistence mode, the load balancing function will +// rely on a cookie established by the backend application. All +// requests carrying the same cookie value will be handled by the +// same Member of the Pool. +type SessionPersistence struct { + // The type of persistence mode. + Type string `json:"type"` + + // Name of cookie if persistence mode is set appropriately. + CookieName string `json:"cookie_name,omitempty"` +} + +// LoadBalancerID represents a load balancer. +type LoadBalancerID struct { + ID string `json:"id"` +} + +// ListenerID represents a listener. +type ListenerID struct { + ID string `json:"id"` +} + +// Pool represents a logical set of devices, such as web servers, that you +// group together to receive and process traffic. The load balancing function +// chooses a Member of the Pool according to the configured load balancing +// method to handle the new requests or connections received on the VIP address. +type Pool struct { + // The load-balancer algorithm, which is round-robin, least-connections, and + // so on. This value, which must be supported, is dependent on the provider. + // Round-robin must be supported. + LBMethod string `json:"lb_algorithm"` + + // The protocol of the Pool, which is TCP, HTTP, or HTTPS. + Protocol string `json:"protocol"` + + // Description for the Pool. + Description string `json:"description"` + + // A list of listeners objects IDs. + Listeners []ListenerID `json:"listeners"` //[]map[string]interface{} + + // A list of member objects IDs. + Members []Member `json:"members"` + + // The ID of associated health monitor. + MonitorID string `json:"healthmonitor_id"` + + // The network on which the members of the Pool will be located. Only members + // that are on this network can be added to the Pool. + SubnetID string `json:"subnet_id"` + + // The administrative state of the Pool, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up"` + + // Pool name. Does not have to be unique. + Name string `json:"name"` + + // The unique ID for the Pool. + ID string `json:"id"` + + // A list of load balancer objects IDs. + Loadbalancers []LoadBalancerID `json:"loadbalancers"` + + // Indicates whether connections in the same session will be processed by the + // same Pool member or not. + Persistence SessionPersistence `json:"session_persistence"` + + // The provisioning status of the pool. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` +} + +// PoolPage is the page returned by a pager when traversing over a +// collection of pools. +type PoolPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of pools has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r PoolPage) NextPageURL() (string, error) { + var s struct { + Links []golangsdk.Link `json:"pools_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a PoolPage struct is empty. +func (r PoolPage) IsEmpty() (bool, error) { + is, err := ExtractPools(r) + return len(is) == 0, err +} + +// ExtractPools accepts a Page struct, specifically a PoolPage struct, +// and extracts the elements into a slice of Pool structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractPools(r pagination.Page) ([]Pool, error) { + var s struct { + Pools []Pool `json:"pools"` + } + err := (r.(PoolPage)).ExtractInto(&s) + return s.Pools, err +} + +type commonResult struct { + golangsdk.Result +} + +// Extract is a function that accepts a result and extracts a pool. +func (r commonResult) Extract() (*Pool, error) { + var s struct { + Pool *Pool `json:"pool"` + } + err := r.ExtractInto(&s) + return s.Pool, err +} + +// CreateResult represents the result of a Create operation. Call its Extract +// method to interpret the result as a Pool. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a Get operation. Call its Extract +// method to interpret the result as a Pool. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an Update operation. Call its Extract +// method to interpret the result as a Pool. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a Delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + golangsdk.ErrResult +} + +// Member represents the application running on a backend server. +type Member struct { + // Name of the Member. + Name string `json:"name"` + + // Weight of Member. + Weight int `json:"weight"` + + // The administrative state of the member, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up"` + + // Owner of the Member. + TenantID string `json:"tenant_id"` + + // Parameter value for the subnet UUID. + SubnetID string `json:"subnet_cidr_id"` + + // The Pool to which the Member belongs. + PoolID string `json:"pool_id"` + + // The IP address of the Member. + Address string `json:"address"` + + // The port on which the application is hosted. + ProtocolPort int `json:"protocol_port"` + + // The unique ID for the Member. + ID string `json:"id"` + + // The provisioning status of the member. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` +} + +// MemberPage is the page returned by a pager when traversing over a +// collection of Members in a Pool. +type MemberPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of members has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r MemberPage) NextPageURL() (string, error) { + var s struct { + Links []golangsdk.Link `json:"members_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a MemberPage struct is empty. +func (r MemberPage) IsEmpty() (bool, error) { + is, err := ExtractMembers(r) + return len(is) == 0, err +} + +// ExtractMembers accepts a Page struct, specifically a MemberPage struct, +// and extracts the elements into a slice of Members structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractMembers(r pagination.Page) ([]Member, error) { + var s struct { + Members []Member `json:"members"` + } + err := (r.(MemberPage)).ExtractInto(&s) + return s.Members, err +} + +type commonMemberResult struct { + golangsdk.Result +} + +// ExtractMember is a function that accepts a result and extracts a member. +func (r commonMemberResult) Extract() (*Member, error) { + var s struct { + Member *Member `json:"member"` + } + err := r.ExtractInto(&s) + return s.Member, err +} + +// CreateMemberResult represents the result of a CreateMember operation. +// Call its Extract method to interpret it as a Member. +type CreateMemberResult struct { + commonMemberResult +} + +// GetMemberResult represents the result of a GetMember operation. +// Call its Extract method to interpret it as a Member. +type GetMemberResult struct { + commonMemberResult +} + +// UpdateMemberResult represents the result of an UpdateMember operation. +// Call its Extract method to interpret it as a Member. +type UpdateMemberResult struct { + commonMemberResult +} + +// DeleteMemberResult represents the result of a DeleteMember operation. +// Call its ExtractErr method to determine if the request succeeded or failed. +type DeleteMemberResult struct { + golangsdk.ErrResult +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/elb/v3/pools/urls.go b/vendor/github.com/huaweicloud/golangsdk/openstack/elb/v3/pools/urls.go new file mode 100644 index 0000000000..1484092e75 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/elb/v3/pools/urls.go @@ -0,0 +1,25 @@ +package pools + +import "github.com/huaweicloud/golangsdk" + +const ( + rootPath = "elb" + resourcePath = "pools" + memberPath = "members" +) + +func rootURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} + +func memberRootURL(c *golangsdk.ServiceClient, poolId string) string { + return c.ServiceURL(rootPath, resourcePath, poolId, memberPath) +} + +func memberResourceURL(c *golangsdk.ServiceClient, poolID string, memeberID string) string { + return c.ServiceURL(rootPath, resourcePath, poolID, memberPath, memeberID) +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/ims/v2/cloudimages/results_job.go b/vendor/github.com/huaweicloud/golangsdk/openstack/ims/v2/cloudimages/results_job.go index ad5e6ab192..aeb589d679 100644 --- a/vendor/github.com/huaweicloud/golangsdk/openstack/ims/v2/cloudimages/results_job.go +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/ims/v2/cloudimages/results_job.go @@ -2,6 +2,7 @@ package cloudimages import ( "fmt" + "time" "github.com/huaweicloud/golangsdk" ) @@ -52,7 +53,7 @@ func WaitForJobSuccess(client *golangsdk.ServiceClient, secs int, jobID string) if err != nil { return false, err } - + time.Sleep(10 * time.Second) if job.Status == "SUCCESS" { return true, nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 3a34dbc331..ea3794f725 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -267,7 +267,7 @@ github.com/hashicorp/terraform-svchost/auth github.com/hashicorp/terraform-svchost/disco # github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d github.com/hashicorp/yamux -# github.com/huaweicloud/golangsdk v0.0.0-20210519120215-6602b8a10c02 +# github.com/huaweicloud/golangsdk v0.0.0-20210521045610-c7bf719c8e93 ## explicit github.com/huaweicloud/golangsdk github.com/huaweicloud/golangsdk/internal @@ -342,6 +342,7 @@ github.com/huaweicloud/golangsdk/openstack/elb/v3/flavors github.com/huaweicloud/golangsdk/openstack/elb/v3/ipgroups github.com/huaweicloud/golangsdk/openstack/elb/v3/listeners github.com/huaweicloud/golangsdk/openstack/elb/v3/loadbalancers +github.com/huaweicloud/golangsdk/openstack/elb/v3/pools github.com/huaweicloud/golangsdk/openstack/eps/v1/enterpriseprojects github.com/huaweicloud/golangsdk/openstack/evs/v2/snapshots github.com/huaweicloud/golangsdk/openstack/evs/v2/tags