diff --git a/docs/resources/networking_vip_associate.md b/docs/resources/networking_vip_associate.md index 32baf54da4..9459df069d 100644 --- a/docs/resources/networking_vip_associate.md +++ b/docs/resources/networking_vip_associate.md @@ -16,7 +16,6 @@ data "huaweicloud_vpc_subnet" "mynet" { resource "huaweicloud_networking_vip" "myvip" { network_id = data.huaweicloud_vpc_subnet.mynet.id - subnet_id = data.huaweicloud_vpc_subnet.mynet.subnet_id } resource "huaweicloud_networking_vip_associate" "vip_associated" { @@ -29,13 +28,12 @@ resource "huaweicloud_networking_vip_associate" "vip_associated" { The following arguments are supported: -* `region` - (Optional) The region in which to obtain the vip associate resource. If omitted, the provider-level region will work as default. Changing this creates a new vip associate resource. +* `region` - (Optional, ForceNew) The region in which to obtain the vip associate resource. + If omitted, the provider-level region will work as default. -* `vip_id` - (Required) The ID of vip to attach the port to. - Changing this creates a new vip associate. +* `vip_id` - (Required, ForceNew) The ID of vip to attach the ports to. * `port_ids` - (Required) An array of one or more IDs of the ports to attach the vip to. - Changing this creates a new vip associate. ## Attributes Reference @@ -45,3 +43,4 @@ The following attributes are exported: * `port_ids` - See Argument Reference above. * `vip_subnet_id` - The ID of the subnet this vip connects to. * `vip_ip_address` - The IP address in the subnet for this vip. +* `ip_addresses` - The IP addresses of ports to attach the vip to. diff --git a/huaweicloud/resource_huaweicloud_networking_vip_associate_v2.go b/huaweicloud/resource_huaweicloud_networking_vip_associate_v2.go index 1af78c0330..bd9b9b5269 100644 --- a/huaweicloud/resource_huaweicloud_networking_vip_associate_v2.go +++ b/huaweicloud/resource_huaweicloud_networking_vip_associate_v2.go @@ -3,15 +3,17 @@ package huaweicloud import ( "fmt" "log" - "strings" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/huaweicloud/golangsdk" "github.com/huaweicloud/golangsdk/openstack/networking/v2/ports" ) func resourceNetworkingVIPAssociateV2() *schema.Resource { return &schema.Resource{ Create: resourceNetworkingVIPAssociateV2Create, + Update: resourceNetworkingVIPAssociateV2Update, Read: resourceNetworkingVIPAssociateV2Read, Delete: resourceNetworkingVIPAssociateV2Delete, @@ -30,10 +32,14 @@ func resourceNetworkingVIPAssociateV2() *schema.Resource { "port_ids": { Type: schema.TypeSet, Required: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, + "ip_addresses": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, "vip_subnet_id": { Type: schema.TypeString, Computed: true, @@ -46,18 +52,6 @@ func resourceNetworkingVIPAssociateV2() *schema.Resource { } } -func parseNetworkingVIPAssociateID(id string) (string, []string, error) { - idParts := strings.Split(id, "/") - if len(idParts) < 2 { - return "", nil, fmt.Errorf("Unable to determine vip association ID") - } - - vipid := idParts[0] - portids := idParts[1:] - - return vipid, portids, nil -} - func resourceNetworkingPortIDs(d *schema.ResourceData) []string { rawPortIDs := d.Get("port_ids").(*schema.Set).List() portids := make([]string, len(rawPortIDs)) @@ -67,128 +61,136 @@ func resourceNetworkingPortIDs(d *schema.ResourceData) []string { return portids } -func resourceNetworkingVIPAssociateV2Create(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - vipid := d.Get("vip_id").(string) - portids := resourceNetworkingPortIDs(d) - - networkingClient, err := config.NetworkingV2Client(GetRegion(d, config)) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud networking client: %s", err) - } +func updateNetworkingVIPAssociate(client *golangsdk.ServiceClient, vipID string, portIDs []string) error { + allAddrs := make([]string, len(portIDs)) - // port by port - fauxid := fmt.Sprintf("%s", vipid) - for _, portid := range portids { - // First get the port information - fauxid = fmt.Sprintf("%s/%s", fauxid, portid) - port, err := ports.Get(networkingClient, portid).Extract() + // check the port id + for i, portid := range portIDs { + port, err := ports.Get(client, portid).Extract() if err != nil { - return CheckDeleted(d, err, "port") + return fmt.Errorf("Error fetching port %s: %s", portid, err) } - ipaddress := "" if len(port.FixedIPs) > 0 { - ipaddress = port.FixedIPs[0].IPAddress - } - if len(ipaddress) == 0 { - return fmt.Errorf("IPAddress is empty, Error associate vip: %#v", port) - } - - // Then get the vip information - vip, err := ports.Get(networkingClient, vipid).Extract() - if err != nil { - return CheckDeleted(d, err, "vip") + allAddrs[i] = port.FixedIPs[0].IPAddress + } else { + return fmt.Errorf("port %s has no ip address, Error associate it", portid) } + } - // Finnaly associate vip to port - // Update VIP AllowedAddressPairs - isfound := false - for _, raw := range vip.AllowedAddressPairs { - if ipaddress == raw.IPAddress { - isfound = true - break - } + // construct allowed address pairs + allowedPairs := make([]ports.AddressPair, len(allAddrs)) + for i, addr := range allAddrs { + allowedPairs[i] = ports.AddressPair{ + IPAddress: addr, } + } + // associate vip to port + associateOpts := ports.UpdateOpts{ + AllowedAddressPairs: &allowedPairs, + } + log.Printf("[DEBUG] VIP Associate %s with options: %#v", vipID, associateOpts) + _, err := ports.Update(client, vipID, associateOpts).Extract() + if err != nil { + return fmt.Errorf("Error associate vip: %s", err) + } - // If IP Address is found, not to update VIP - if !isfound { - pairs := make([]ports.AddressPair, len(vip.AllowedAddressPairs)+1) - for i, raw := range vip.AllowedAddressPairs { - pairs[i] = ports.AddressPair{ - IPAddress: raw.IPAddress, - MACAddress: raw.MACAddress, - } - } - pairs[len(vip.AllowedAddressPairs)] = ports.AddressPair{ - IPAddress: ipaddress, - } - associateOpts := ports.UpdateOpts{ - AllowedAddressPairs: &pairs, - } + // Update the allowed-address-pairs of the port to 1.1.1.1/0 + // to disable the source/destination check + portpairs := make([]ports.AddressPair, 1) + portpairs[0] = ports.AddressPair{ + IPAddress: "1.1.1.1/0", + } + portUpdateOpts := ports.UpdateOpts{ + AllowedAddressPairs: &portpairs, + } - log.Printf("[DEBUG] VIP Associate %s with options: %#v", vipid, associateOpts) - _, err = ports.Update(networkingClient, vipid, associateOpts).Extract() - if err != nil { - return fmt.Errorf("Error associate vip: %s", err) - } + for _, portid := range portIDs { + _, err = ports.Update(client, portid, portUpdateOpts).Extract() + if err != nil { + return fmt.Errorf("Error update port %s: %s", portid, err) } + } - // Update Port AllowedAddressPairs - portspairs := make([]ports.AddressPair, 1) - portspairs[0] = ports.AddressPair{ - IPAddress: "1.1.1.1/0", - } - portUpdateOpts := ports.UpdateOpts{ - AllowedAddressPairs: &portspairs, - } + return nil +} - log.Printf("[DEBUG] Port Update %s with options: %#v", vipid, portUpdateOpts) - _, err = ports.Update(networkingClient, portid, portUpdateOpts).Extract() - if err != nil { - return fmt.Errorf("Error update port: %s", err) - } +func resourceNetworkingVIPAssociateV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.NetworkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud networking client: %s", err) } - // There's no assciate vip id, therefore a faux ID will be used. - d.SetId(fauxid) + // chech the vip + vipID := d.Get("vip_id").(string) + _, err = ports.Get(networkingClient, vipID).Extract() + if err != nil { + return fmt.Errorf("Error fetching vip %s: %s", vipID, err) + } + + portids := resourceNetworkingPortIDs(d) + if err = updateNetworkingVIPAssociate(networkingClient, vipID, portids); err != nil { + return err + } + // set id + d.SetId(hashcode.Strings(portids)) return resourceNetworkingVIPAssociateV2Read(d, meta) } -func resourceNetworkingVIPAssociateV2Read(d *schema.ResourceData, meta interface{}) error { +func resourceNetworkingVIPAssociateV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - // Obtain relevant info from parsing the ID - vipid, portids, err := parseNetworkingVIPAssociateID(d.Id()) + networkingClient, err := config.NetworkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud networking client: %s", err) + } + + // chech the vip + vipID := d.Get("vip_id").(string) + _, err = ports.Get(networkingClient, vipID).Extract() if err != nil { + return fmt.Errorf("Error fetching vip %s: %s", vipID, err) + } + + portids := resourceNetworkingPortIDs(d) + if err = updateNetworkingVIPAssociate(networkingClient, vipID, portids); err != nil { return err } - // First see if the port still exists + return resourceNetworkingVIPAssociateV2Read(d, meta) +} + +func resourceNetworkingVIPAssociateV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) networkingClient, err := config.NetworkingV2Client(GetRegion(d, config)) if err != nil { return fmt.Errorf("Error creating HuaweiCloud networking client: %s", err) } - // Then try to do this by querying the vip API. - vip, err := ports.Get(networkingClient, vipid).Extract() + vipID := d.Get("vip_id").(string) + // check the vip port + vip, err := ports.Get(networkingClient, vipID).Extract() if err != nil { return CheckDeleted(d, err, "vip") } - // port by port - newportids := make(map[string]string) + var allPorts []string + var allAddrs []string + // check the port still exists + portids := resourceNetworkingPortIDs(d) for _, portid := range portids { p, err := ports.Get(networkingClient, portid).Extract() if err != nil { - return CheckDeleted(d, err, "port") + log.Printf("[WARN] failed to fetch port %s: %s", portid, err) + continue } for _, ip := range p.FixedIPs { for _, addresspair := range vip.AllowedAddressPairs { if ip.IPAddress == addresspair.IPAddress { - // Associated - newportids[portid] = portid + allPorts = append(allPorts, portid) + allAddrs = append(allAddrs, ip.IPAddress) break } } @@ -196,24 +198,18 @@ func resourceNetworkingVIPAssociateV2Read(d *schema.ResourceData, meta interface } // if no port is associated - if len(newportids) == 0 { + if len(allPorts) == 0 { + log.Printf("[WARN] no port is associated with vip %s", vipID) d.SetId("") return nil } - // convert results from map to array - newresults := make([]string, len(newportids)) - var index = 0 - for newvalue := range newportids { - newresults[index] = newvalue - index++ - } - // Set the attributes pulled from the composed resource ID - d.Set("vip_id", vipid) - d.Set("port_ids", newresults) + d.Set("vip_id", vipID) d.Set("vip_subnet_id", vip.FixedIPs[0].SubnetID) d.Set("vip_ip_address", vip.FixedIPs[0].IPAddress) + d.Set("port_ids", allPorts) + d.Set("ip_addresses", allAddrs) return nil } @@ -225,70 +221,24 @@ func resourceNetworkingVIPAssociateV2Delete(d *schema.ResourceData, meta interfa return fmt.Errorf("Error creating HuaweiCloud networking client: %s", err) } - // Obtain relevant info from parsing the ID - id := d.Id() - vipid, portids, err := parseNetworkingVIPAssociateID(id) + vipID := d.Get("vip_id").(string) + // check the vip port + _, err = ports.Get(networkingClient, vipID).Extract() if err != nil { - return err + return CheckDeleted(d, err, "vip") } - // port by port - for _, portid := range portids { - // First get the port information - port, err := ports.Get(networkingClient, portid).Extract() - if err != nil { - return CheckDeleted(d, err, "port") - } - - ipaddress := "" - if len(port.FixedIPs) > 0 { - ipaddress = port.FixedIPs[0].IPAddress - } - if len(ipaddress) == 0 { - return fmt.Errorf("IPAddress is empty, Error disassociate vip: %#v", port) - } - - // Then get the vip information - vip, err := ports.Get(networkingClient, vipid).Extract() - if err != nil { - return CheckDeleted(d, err, "vip") - } - - // Update VIP AllowedAddressPairs - isfound := false - for _, raw := range vip.AllowedAddressPairs { - if ipaddress == raw.IPAddress { - isfound = true - break - } - } - - // If IP Address is found, need to update VIP - if isfound { - pairs := make([]ports.AddressPair, len(vip.AllowedAddressPairs)-1) - i := 0 - for _, raw := range vip.AllowedAddressPairs { - if ipaddress != raw.IPAddress { - pairs[i] = ports.AddressPair{ - IPAddress: raw.IPAddress, - MACAddress: raw.MACAddress, - } - i++ - } - } - disassociateOpts := ports.UpdateOpts{ - AllowedAddressPairs: &pairs, - } - - log.Printf("[DEBUG] VIP Disassociate %s with options: %#v", vipid, disassociateOpts) - _, err = ports.Update(networkingClient, vipid, disassociateOpts).Extract() - if err != nil { - return fmt.Errorf("Error disassociate vip: %s", err) - } - } + // disassociate all allowed address pairs + allowedPairs := make([]ports.AddressPair, 0) + disassociateOpts := ports.UpdateOpts{ + AllowedAddressPairs: &allowedPairs, + } + log.Printf("[DEBUG] Disassociate all ports with %s", vipID) + _, err = ports.Update(networkingClient, vipID, disassociateOpts).Extract() + if err != nil { + return fmt.Errorf("Error disassociate vip: %s", err) } d.SetId("") - log.Printf("[DEBUG] Successfully disassociate vip (%s)", id) return nil } diff --git a/huaweicloud/resource_huaweicloud_networking_vip_associate_v2_test.go b/huaweicloud/resource_huaweicloud_networking_vip_associate_v2_test.go index f8a6e449ff..63b218a4c5 100644 --- a/huaweicloud/resource_huaweicloud_networking_vip_associate_v2_test.go +++ b/huaweicloud/resource_huaweicloud_networking_vip_associate_v2_test.go @@ -5,6 +5,7 @@ import ( "log" "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" @@ -12,6 +13,7 @@ import ( ) func TestAccNetworkingV2VIPAssociate_basic(t *testing.T) { + rand := acctest.RandString(5) var vip ports.Port var port1 ports.Port var port2 ports.Port @@ -22,13 +24,13 @@ func TestAccNetworkingV2VIPAssociate_basic(t *testing.T) { CheckDestroy: testAccCheckNetworkingV2VIPAssociateDestroy, Steps: []resource.TestStep{ { - Config: TestAccNetworkingV2VIPAssociateConfig_basic, + Config: testAccNetworkingV2VIPAssociateConfig_basic(rand), Check: resource.ComposeTestCheckFunc( - testAccCheckNetworkingV2PortExists("huaweicloud_networking_port_v2.port_1", &port1), - testAccCheckNetworkingV2PortExists("huaweicloud_networking_port_v2.port_2", &port2), - testAccCheckNetworkingV2VIPExists("huaweicloud_networking_vip_v2.vip_1", &vip), - testAccCheckNetworkingV2VIPAssociateAssociated(&port1, &vip), - testAccCheckNetworkingV2VIPAssociateAssociated(&port2, &vip), + testAccCheckNetworkingV2PortExists("huaweicloud_networking_port.port_1", &port1), + testAccCheckNetworkingV2PortExists("huaweicloud_networking_port.port_2", &port2), + testAccCheckNetworkingV2VIPExists("huaweicloud_networking_vip.vip_1", &vip), + testAccCheckNetworkingV2VIPAssociated(&port1, &vip), + testAccCheckNetworkingV2VIPAssociated(&port2, &vip), ), }, }, @@ -43,16 +45,12 @@ func testAccCheckNetworkingV2VIPAssociateDestroy(s *terraform.State) error { } for _, rs := range s.RootModule().Resources { - if rs.Type != "huaweicloud_networking_vip_associate_v2" { + if rs.Type != "huaweicloud_networking_vip_associate" { continue } - vipid, portids, err := parseNetworkingVIPAssociateID(rs.Primary.ID) - if err != nil { - return err - } - - vipport, err := ports.Get(networkingClient, vipid).Extract() + vipID := rs.Primary.Attributes["vip_id"] + _, err = ports.Get(networkingClient, vipID).Extract() if err != nil { // If the error is a 404, then the vip port does not exist, // and therefore the floating IP cannot be associated to it. @@ -61,35 +59,13 @@ func testAccCheckNetworkingV2VIPAssociateDestroy(s *terraform.State) error { } return err } - - // port by port - for _, portid := range portids { - p, err := ports.Get(networkingClient, portid).Extract() - if err != nil { - // If the error is a 404, then the port does not exist, - // and therefore the floating IP cannot be associated to it. - if _, ok := err.(golangsdk.ErrDefault404); ok { - return nil - } - return err - } - - // But if the port and vip still exists - for _, ip := range p.FixedIPs { - for _, addresspair := range vipport.AllowedAddressPairs { - if ip.IPAddress == addresspair.IPAddress { - return fmt.Errorf("VIP %s is still associated to port %s", vipid, portid) - } - } - } - } } log.Printf("[DEBUG] testAccCheckNetworkingV2VIPAssociateDestroy success!") return nil } -func testAccCheckNetworkingV2VIPAssociateAssociated(p *ports.Port, vip *ports.Port) resource.TestCheckFunc { +func testAccCheckNetworkingV2VIPAssociated(p *ports.Port, vip *ports.Port) resource.TestCheckFunc { return func(s *terraform.State) error { config := testAccProvider.Meta().(*Config) networkingClient, err := config.NetworkingV2Client(OS_REGION_NAME) @@ -120,7 +96,7 @@ func testAccCheckNetworkingV2VIPAssociateAssociated(p *ports.Port, vip *ports.Po for _, ip := range p.FixedIPs { for _, addresspair := range vipport.AllowedAddressPairs { if ip.IPAddress == addresspair.IPAddress { - log.Printf("[DEBUG] testAccCheckNetworkingV2VIPAssociateAssociated success!") + log.Printf("[DEBUG] testAccCheckNetworkingV2VIPAssociated success!") return nil } } @@ -130,76 +106,45 @@ func testAccCheckNetworkingV2VIPAssociateAssociated(p *ports.Port, vip *ports.Po } } -var TestAccNetworkingV2VIPAssociateConfig_basic = fmt.Sprintf(` -resource "huaweicloud_networking_network_v2" "network_1" { - name = "network_1" - admin_state_up = "true" +func testAccNetworkingV2VIPAssociateConfig_basic(rand string) string { + return fmt.Sprintf(` +resource "huaweicloud_vpc" "vpc_1" { + name = "acc-test-vpc-%s" + cidr = "192.168.0.0/16" } -resource "huaweicloud_networking_subnet_v2" "subnet_1" { - name = "subnet_1" - cidr = "192.168.199.0/24" - ip_version = 4 - network_id = "${huaweicloud_networking_network_v2.network_1.id}" +resource "huaweicloud_vpc_subnet" "subnet_1" { + vpc_id = huaweicloud_vpc.vpc_1.id + name = "acc-test-subnet-%s" + cidr = "192.168.0.0/24" + gateway_ip = "192.168.0.1" } -resource "huaweicloud_networking_router_interface_v2" "router_interface_1" { - router_id = "${huaweicloud_networking_router_v2.router_1.id}" - subnet_id = "${huaweicloud_networking_subnet_v2.subnet_1.id}" -} - -resource "huaweicloud_networking_router_v2" "router_1" { - name = "router_1" - external_network_id = "%s" -} +resource "huaweicloud_networking_port" "port_1" { + name = "port_1" + network_id = huaweicloud_vpc_subnet.subnet_1.id -resource "huaweicloud_networking_port_v2" "port_1" { - name = "port_1" - admin_state_up = "true" - network_id = "${huaweicloud_networking_network_v2.network_1.id}" - fixed_ip { - subnet_id = "${huaweicloud_networking_subnet_v2.subnet_1.id}" + subnet_id = huaweicloud_vpc_subnet.subnet_1.subnet_id } } -resource "huaweicloud_compute_instance_v2" "instance_1" { - name = "instance_1" - security_groups = ["default"] - availability_zone = "%s" - - network { - port = "${huaweicloud_networking_port_v2.port_1.id}" - } -} +resource "huaweicloud_networking_port" "port_2" { + name = "port_2" + network_id = huaweicloud_vpc_subnet.subnet_1.id -resource "huaweicloud_networking_port_v2" "port_2" { - name = "port_2" - admin_state_up = "true" - network_id = "${huaweicloud_networking_network_v2.network_1.id}" - fixed_ip { - subnet_id = "${huaweicloud_networking_subnet_v2.subnet_1.id}" + subnet_id = huaweicloud_vpc_subnet.subnet_1.subnet_id } } -resource "huaweicloud_compute_instance_v2" "instance_2" { - name = "instance_2" - security_groups = ["default"] - availability_zone = "%s" - - network { - port = "${huaweicloud_networking_port_v2.port_2.id}" - } +resource "huaweicloud_networking_vip" "vip_1" { + network_id = huaweicloud_vpc_subnet.subnet_1.id } -resource "huaweicloud_networking_vip_v2" "vip_1" { - network_id = "${huaweicloud_networking_network_v2.network_1.id}" - subnet_id = "${huaweicloud_networking_subnet_v2.subnet_1.id}" +resource "huaweicloud_networking_vip_associate" "vip_associate_1" { + vip_id = huaweicloud_networking_vip.vip_1.id + port_ids = [huaweicloud_networking_port.port_1.id, huaweicloud_networking_port.port_2.id] } - -resource "huaweicloud_networking_vip_associate_v2" "vip_associate_1" { - vip_id = "${huaweicloud_networking_vip_v2.vip_1.id}" - port_ids = ["${huaweicloud_networking_port_v2.port_1.id}", "${huaweicloud_networking_port_v2.port_2.id}"] + `, rand, rand) } -`, OS_EXTGW_ID, OS_AVAILABILITY_ZONE, OS_AVAILABILITY_ZONE)