diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go index b09c9c8404..94a43a49db 100644 --- a/huaweicloud/provider.go +++ b/huaweicloud/provider.go @@ -328,6 +328,8 @@ func Provider() terraform.ResourceProvider { "huaweicloud_vpnaas_site_connection_v2": resourceVpnSiteConnectionV2(), "huaweicloud_dli_queue_v1": resourceDliQueueV1(), "huaweicloud_cs_route_v1": resourceCsRouteV1(), + "huaweicloud_networking_vip_v2": resourceNetworkingVIPV2(), + "huaweicloud_networking_vip_associate_v2": resourceNetworkingVIPAssociateV2(), }, ConfigureFunc: configureProvider, diff --git a/huaweicloud/resource_huaweicloud_networking_port_v2.go b/huaweicloud/resource_huaweicloud_networking_port_v2.go index 6533cb3852..3f6ec41b90 100644 --- a/huaweicloud/resource_huaweicloud_networking_port_v2.go +++ b/huaweicloud/resource_huaweicloud_networking_port_v2.go @@ -110,6 +110,7 @@ func resourceNetworkingPortV2() *schema.Resource { Type: schema.TypeSet, Optional: true, ForceNew: false, + Computed: true, Set: allowedAddressPairsHash, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ diff --git a/huaweicloud/resource_huaweicloud_networking_vip_associate_v2.go b/huaweicloud/resource_huaweicloud_networking_vip_associate_v2.go new file mode 100644 index 0000000000..578292118a --- /dev/null +++ b/huaweicloud/resource_huaweicloud_networking_vip_associate_v2.go @@ -0,0 +1,288 @@ +package huaweicloud + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/ports" +) + +func resourceNetworkingVIPAssociateV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingVIPAssociateV2Create, + Read: resourceNetworkingVIPAssociateV2Read, + Delete: resourceNetworkingVIPAssociateV2Delete, + + Schema: map[string]*schema.Schema{ + "vip_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "port_ids": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "vip_subnet_id": { + Type: schema.TypeString, + Computed: true, + }, + "vip_ip_address": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +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)) + for i, raw := range rawPortIDs { + portids[i] = raw.(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) + } + + // 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() + 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 associate vip: %#v", port) + } + + // Then get the vip information + vip, err := ports.Get(networkingClient, vipid).Extract() + if err != nil { + return CheckDeleted(d, err, "vip") + } + + // Finnaly associate vip to port + // Update VIP AllowedAddressPairs + isfound := false + for _, raw := range vip.AllowedAddressPairs { + if ipaddress == raw.IPAddress { + isfound = true + break + } + } + + // 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, + } + + 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) + } + } + + // Update Port AllowedAddressPairs + portspairs := make([]ports.AddressPair, 1) + portspairs[0] = ports.AddressPair{ + IPAddress: "1.1.1.1/0", + } + portUpdateOpts := ports.UpdateOpts{ + AllowedAddressPairs: &portspairs, + } + + 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) + } + } + + // There's no assciate vip id, therefore a faux ID will be used. + d.SetId(fauxid) + + return resourceNetworkingVIPAssociateV2Read(d, meta) +} + +func resourceNetworkingVIPAssociateV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + // Obtain relevant info from parsing the ID + vipid, portids, err := parseNetworkingVIPAssociateID(d.Id()) + if err != nil { + return err + } + + // First see if the port still exists + 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() + if err != nil { + return CheckDeleted(d, err, "vip") + } + + // port by port + newportids := make(map[string]string) + for _, portid := range portids { + p, err := ports.Get(networkingClient, portid).Extract() + if err != nil { + return CheckDeleted(d, err, "port") + } + + for _, ip := range p.FixedIPs { + for _, addresspair := range vip.AllowedAddressPairs { + if ip.IPAddress == addresspair.IPAddress { + // Associated + newportids[portid] = portid + break + } + } + } + } + + // if no port is associated + if len(newportids) == 0 { + 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_subnet_id", vip.FixedIPs[0].SubnetID) + d.Set("vip_ip_address", vip.FixedIPs[0].IPAddress) + + return nil +} + +func resourceNetworkingVIPAssociateV2Delete(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) + } + + // Obtain relevant info from parsing the ID + id := d.Id() + vipid, portids, err := parseNetworkingVIPAssociateID(id) + if err != nil { + return err + } + + // 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) + } + } + } + + 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 new file mode 100644 index 0000000000..f6baf0d59d --- /dev/null +++ b/huaweicloud/resource_huaweicloud_networking_vip_associate_v2_test.go @@ -0,0 +1,205 @@ +package huaweicloud + +import ( + "fmt" + "log" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/ports" +) + +func TestAccNetworkingV2VIPAssociate_basic(t *testing.T) { + var vip ports.Port + var port1 ports.Port + var port2 ports.Port + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2VIPAssociateDestroy, + Steps: []resource.TestStep{ + { + Config: TestAccNetworkingV2VIPAssociateConfig_basic, + 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), + ), + }, + }, + }) +} + +func testAccCheckNetworkingV2VIPAssociateDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "huaweicloud_networking_vip_associate_v2" { + continue + } + + vipid, portids, err := parseNetworkingVIPAssociateID(rs.Primary.ID) + if err != nil { + return err + } + + vipport, 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. + if _, ok := err.(golangsdk.ErrDefault404); ok { + return nil + } + 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 { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud networking client: %s", err) + } + + p, err := ports.Get(networkingClient, p.ID).Extract() + if err != nil { + // If the error is a 404, then the port does not exist, + // and therefore the VIP cannot be associated to it. + if _, ok := err.(golangsdk.ErrDefault404); ok { + return nil + } + return err + } + + vipport, err := ports.Get(networkingClient, vip.ID).Extract() + if err != nil { + // If the error is a 404, then the vip port does not exist, + // and therefore the VIP cannot be associated to it. + if _, ok := err.(golangsdk.ErrDefault404); ok { + return nil + } + return err + } + + for _, ip := range p.FixedIPs { + for _, addresspair := range vipport.AllowedAddressPairs { + if ip.IPAddress == addresspair.IPAddress { + log.Printf("[DEBUG] testAccCheckNetworkingV2VIPAssociateAssociated success!") + return nil + } + } + } + + return fmt.Errorf("VIP %s was not attached to port %s", vipport.ID, p.ID) + } +} + +var TestAccNetworkingV2VIPAssociateConfig_basic = fmt.Sprintf(` +resource "huaweicloud_networking_network_v2" "network_1" { + name = "network_1" + admin_state_up = "true" +} + +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_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_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}" + } +} + +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_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}" + } +} + +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_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_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}"] +} +`, OS_EXTGW_ID, OS_AVAILABILITY_ZONE, OS_AVAILABILITY_ZONE) diff --git a/huaweicloud/resource_huaweicloud_networking_vip_v2.go b/huaweicloud/resource_huaweicloud_networking_vip_v2.go new file mode 100644 index 0000000000..7de57e5e22 --- /dev/null +++ b/huaweicloud/resource_huaweicloud_networking_vip_v2.go @@ -0,0 +1,197 @@ +package huaweicloud + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/ports" +) + +func resourceNetworkingVIPV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingVIPV2Create, + Read: resourceNetworkingVIPV2Read, + Delete: resourceNetworkingVIPV2Delete, + + Schema: map[string]*schema.Schema{ + "network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "subnet_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "ip_address": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + "device_owner": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceNetworkingVIPV2Create(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) + } + + // Contruct CreateOpts + fixip := make([]ports.IP, 1) + fixip[0] = ports.IP{ + SubnetID: d.Get("subnet_id").(string), + IPAddress: d.Get("ip_address").(string), + } + createOpts := ports.CreateOpts{ + Name: d.Get("name").(string), + NetworkID: d.Get("network_id").(string), + FixedIPs: fixip, + DeviceOwner: "neutron:VIP_PORT", + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + vip, err := ports.Create(networkingClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud Network VIP: %s", err) + } + log.Printf("[DEBUG] Waiting for HuaweiCloud Network VIP (%s) to become available.", vip.ID) + + stateConf := &resource.StateChangeConf{ + Target: []string{"ACTIVE"}, + Refresh: waitForNetworkVIPActive(networkingClient, vip.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + + d.SetId(vip.ID) + + return resourceNetworkingVIPV2Read(d, meta) +} + +func resourceNetworkingVIPV2Read(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) + } + + vip, err := ports.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "vip") + } + + log.Printf("[DEBUG] Retrieved VIP %s: %+v", d.Id(), vip) + + // Computed values + d.Set("network_id", vip.NetworkID) + if len(vip.FixedIPs) > 0 { + d.Set("subnet_id", vip.FixedIPs[0].SubnetID) + d.Set("ip_address", vip.FixedIPs[0].IPAddress) + } else { + d.Set("subnet_id", "") + d.Set("ip_address", "") + } + d.Set("name", vip.Name) + d.Set("status", vip.Status) + d.SetId(vip.ID) + d.Set("tenant_id", vip.TenantID) + d.Set("device_owner", vip.DeviceOwner) + + return nil +} + +func resourceNetworkingVIPV2Delete(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) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: []string{"DELETED"}, + Refresh: waitForNetworkVIPDelete(networkingClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error deleting HuaweiCloud Network VIP: %s", err) + } + + d.SetId("") + return nil +} + +func waitForNetworkVIPActive(networkingClient *golangsdk.ServiceClient, vipid string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + p, err := ports.Get(networkingClient, vipid).Extract() + if err != nil { + return nil, "", err + } + + log.Printf("[DEBUG] HuaweiCloud Network Port: %+v", p) + if p.Status == "DOWN" || p.Status == "ACTIVE" { + return p, "ACTIVE", nil + } + + return p, p.Status, nil + } +} + +func waitForNetworkVIPDelete(networkingClient *golangsdk.ServiceClient, vipid string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete HuaweiCloud Network VIP %s", vipid) + + p, err := ports.Get(networkingClient, vipid).Extract() + if err != nil { + if _, ok := err.(golangsdk.ErrDefault404); ok { + log.Printf("[DEBUG] Successfully deleted HuaweiCloud VIP %s", vipid) + return p, "DELETED", nil + } + return p, "ACTIVE", err + } + + err = ports.Delete(networkingClient, vipid).ExtractErr() + if err != nil { + if _, ok := err.(golangsdk.ErrDefault404); ok { + log.Printf("[DEBUG] Successfully deleted HuaweiCloud VIP %s", vipid) + return p, "DELETED", nil + } + return p, "ACTIVE", err + } + + log.Printf("[DEBUG] HuaweiCloud VIP %s still active.\n", vipid) + return p, "ACTIVE", nil + } +} diff --git a/huaweicloud/resource_huaweicloud_networking_vip_v2_test.go b/huaweicloud/resource_huaweicloud_networking_vip_v2_test.go new file mode 100644 index 0000000000..3f57d76438 --- /dev/null +++ b/huaweicloud/resource_huaweicloud_networking_vip_v2_test.go @@ -0,0 +1,113 @@ +package huaweicloud + +import ( + "fmt" + "log" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/ports" +) + +func TestAccNetworkingV2VIP_basic(t *testing.T) { + var vip ports.Port + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2VIPDestroy, + Steps: []resource.TestStep{ + { + Config: TestAccNetworkingV2VIPConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2VIPExists("huaweicloud_networking_vip_v2.vip_1", &vip), + ), + }, + }, + }) +} + +func testAccCheckNetworkingV2VIPDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "huaweicloud_networking_vip_v2" { + continue + } + + _, err := ports.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("VIP still exists") + } + } + + log.Printf("[DEBUG] testAccCheckNetworkingV2VIPDestroy success!") + + return nil +} + +func testAccCheckNetworkingV2VIPExists(n string, vip *ports.Port) 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) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud networking client: %s", err) + } + + found, err := ports.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("VIP not found") + } + log.Printf("[DEBUG] test found is: %#v", found) + *vip = *found + + return nil + } +} + +var TestAccNetworkingV2VIPConfig_basic = fmt.Sprintf(` +resource "huaweicloud_networking_network_v2" "network_1" { + name = "network_1" + admin_state_up = "true" +} + +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_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_vip_v2" "vip_1" { + network_id = "${huaweicloud_networking_network_v2.network_1.id}" + subnet_id = "${huaweicloud_networking_subnet_v2.subnet_1.id}" +} +`, OS_EXTGW_ID) diff --git a/website/docs/r/networking_vip_associate_v2.html.markdown b/website/docs/r/networking_vip_associate_v2.html.markdown new file mode 100644 index 0000000000..997b6bc163 --- /dev/null +++ b/website/docs/r/networking_vip_associate_v2.html.markdown @@ -0,0 +1,104 @@ +--- +layout: "huaweicloud" +page_title: "HuaweiCloud: huaweicloud_networking_vip_associate_v2" +sidebar_current: "docs-huaweicloud-resource-networking-vip-associate-v2" +description: |- + Manages a V2 vip associate resource within HuaweiCloud. +--- + +# huaweicloud\_networking\_vip_associate_v2 + +Manages a V2 vip associate resource within HuaweiCloud. + +## Example Usage + +```hcl +resource "huaweicloud_networking_network_v2" "network_1" { + name = "network_1" + admin_state_up = "true" +} + +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_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 = "0a2228f2-7f8a-45f1-8e09-9039e1d09975" +} + +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}" + } +} + +resource "huaweicloud_compute_instance_v2" "instance_1" { + name = "instance_1" + security_groups = ["default"] + + network { + port = "${huaweicloud_networking_port_v2.port_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}" + } +} + +resource "huaweicloud_compute_instance_v2" "instance_2" { + name = "instance_2" + security_groups = ["default"] + + network { + port = "${huaweicloud_networking_port_v2.port_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_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}"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `vip_id` - (Required) The ID of vip to attach the port to. + Changing this creates a new vip associate. + +* `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 + +The following attributes are exported: + +* `vip_id` - See Argument Reference above. +* `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. diff --git a/website/docs/r/networking_vip_v2.html.markdown b/website/docs/r/networking_vip_v2.html.markdown new file mode 100644 index 0000000000..d820af9b78 --- /dev/null +++ b/website/docs/r/networking_vip_v2.html.markdown @@ -0,0 +1,71 @@ +--- +layout: "huaweicloud" +page_title: "HuaweiCloud: huaweicloud_networking_vip_v2" +sidebar_current: "docs-huaweicloud-resource-networking-vip-v2" +description: |- + Manages a V2 vip resource within HuaweiCloud. +--- + +# huaweicloud\_networking\_vip_v2 + +Manages a V2 vip resource within HuaweiCloud. + +## Example Usage + +```hcl +resource "huaweicloud_networking_network_v2" "network_1" { + name = "network_1" + admin_state_up = "true" +} + +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_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 = "0a2228f2-7f8a-45f1-8e09-9039e1d09975" +} + +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}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `network_id` - (Required) The ID of the network to attach the vip to. + Changing this creates a new vip. + +* `subnet_id` - (Required) Subnet in which to allocate IP address for this vip. + Changing this creates a new vip. + +* `ip_address` - (Optional) IP address desired in the subnet for this vip. + If you don't specify `ip_address`, an available IP address from + the specified subnet will be allocated to this vip. + +* `name` - (Optional) A unique name for the vip. + +## Attributes Reference + +The following attributes are exported: + +* `network_id` - See Argument Reference above. +* `subnet_id` - See Argument Reference above. +* `ip_address` - See Argument Reference above. +* `name` - See Argument Reference above. +* `status` - The status of vip. +* `id` - The ID of the vip. +* `tenant_id` - The tenant ID of the vip. +* `device_owner` - The device owner of the vip. diff --git a/website/huaweicloud.erb b/website/huaweicloud.erb index 7944712893..7df10e92fe 100644 --- a/website/huaweicloud.erb +++ b/website/huaweicloud.erb @@ -524,6 +524,12 @@