From 93148a011712517785c5953461168124f4e0bfcd Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Fri, 14 Aug 2026 14:06:41 -0400 Subject: [PATCH 1/5] Add support for internal LB --- .../resource_cloudstack_loadbalancer_rule.go | 29 ++++++-- ...ource_cloudstack_loadbalancer_rule_test.go | 66 +++++++++++++++++++ 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/cloudstack/resource_cloudstack_loadbalancer_rule.go b/cloudstack/resource_cloudstack_loadbalancer_rule.go index 6ebf52b5..b15200ed 100644 --- a/cloudstack/resource_cloudstack_loadbalancer_rule.go +++ b/cloudstack/resource_cloudstack_loadbalancer_rule.go @@ -50,9 +50,14 @@ func resourceCloudStackLoadBalancerRule() *schema.Resource { Computed: true, }, + // Optional, not Required: CloudStack's own createLoadBalancerRule + // marks publicipid as optional -- an internal LB in a VPC (no + // public IP at all, just network_id) is a real, supported case. + // See verifyLoadBalancerRule, which enforces that at least one of + // ip_address_id/network_id is set instead. "ip_address_id": { Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, }, @@ -163,8 +168,12 @@ func resourceCloudStackLoadBalancerRuleCreate(d *schema.ResourceData, meta inter p.SetCidrlist(cidrList) } - // Set the ipaddress id - p.SetPublicipid(d.Get("ip_address_id").(string)) + // Set the ipaddress id, when given -- omitted entirely for an internal + // LB (network_id-only, no public IP), matching real CloudStack's own + // optional publicipid semantics. + if ipAddressID, ok := d.GetOk("ip_address_id"); ok { + p.SetPublicipid(ipAddressID.(string)) + } // Create the load balancer rule r, err := cs.LoadBalancer.CreateLoadBalancerRule(p) @@ -230,7 +239,13 @@ func resourceCloudStackLoadBalancerRuleRead(d *schema.ResourceData, meta interfa } d.Set("name", lb.Name) - d.Set("ip_address_id", lb.Publicipid) + // Only set ip_address_id if the user specified it, mirroring network_id's + // own guard below -- an internal LB's lb.Publicipid comes back empty + // from CloudStack, and setting that explicitly would fight the schema's + // Optional (no Computed) declaration. + if _, ok := d.GetOk("ip_address_id"); ok { + d.Set("ip_address_id", lb.Publicipid) + } d.Set("algorithm", lb.Algorithm) d.Set("public_port", public_port) d.Set("private_port", private_port) @@ -534,6 +549,12 @@ func resourceCloudStackLoadBalancerRuleDelete(d *schema.ResourceData, meta inter } func verifyLoadBalancerRule(d *schema.ResourceData) error { + _, hasIP := d.GetOk("ip_address_id") + _, hasNetwork := d.GetOk("network_id") + if !hasIP && !hasNetwork { + return fmt.Errorf("at least one of ip_address_id or network_id must be set") + } + if protocol, ok := d.GetOk("protocol"); ok { protocol := protocol.(string) diff --git a/cloudstack/resource_cloudstack_loadbalancer_rule_test.go b/cloudstack/resource_cloudstack_loadbalancer_rule_test.go index 8a9c7920..ab24f608 100644 --- a/cloudstack/resource_cloudstack_loadbalancer_rule_test.go +++ b/cloudstack/resource_cloudstack_loadbalancer_rule_test.go @@ -210,6 +210,36 @@ func TestAccCloudStackLoadBalancerRule_vpcUpdate(t *testing.T) { }) } +// TestAccCloudStackLoadBalancerRule_internal exercises a pure internal LB: +// network_id set, ip_address_id omitted entirely -- no public IP at all. +// Real CloudStack's createLoadBalancerRule marks publicipid optional for +// exactly this VPC-internal case; before this fix the schema's +// Required:true on ip_address_id made it impossible to even plan such a +// config, regardless of what the real API allowed. +func TestAccCloudStackLoadBalancerRule_internal(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackLoadBalancerRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackLoadBalancerRule_internal, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackLoadBalancerRuleExist("cloudstack_loadbalancer_rule.foo", nil), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer_rule.foo", "name", "terraform-ilb"), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer_rule.foo", "ip_address_id", ""), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer_rule.foo", "public_port", "8080"), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer_rule.foo", "private_port", "8080"), + ), + }, + }, + }) +} + func testAccCheckCloudStackLoadBalancerRuleExist(n string, id *string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -466,3 +496,39 @@ resource "cloudstack_loadbalancer_rule" "foo" { member_ids = [cloudstack_instance.foobar1.id, cloudstack_instance.foobar2.id] cidrlist = ["20.0.0.0/8"] }` + +const testAccCloudStackLoadBalancerRule_internal = ` +resource "cloudstack_vpc" "foo" { + name = "terraform-vpc" + cidr = "10.0.0.0/8" + vpc_offering = "Default VPC offering" + zone = "Sandbox-simulator" +} + +resource "cloudstack_network" "foo" { + name = "terraform-network" + display_text = "terraform-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingForVpcNetworks" + vpc_id = cloudstack_vpc.foo.id + zone = cloudstack_vpc.foo.zone +} + +resource "cloudstack_instance" "foobar1" { + name = "terraform-server1" + display_name = "terraform" + service_offering= "Small Instance" + network_id = cloudstack_network.foo.id + template = "CentOS 5.6 (64-bit) no GUI (Simulator)" + zone = cloudstack_network.foo.zone + expunge = true +} + +resource "cloudstack_loadbalancer_rule" "foo" { + name = "terraform-ilb" + algorithm = "roundrobin" + network_id = cloudstack_network.foo.id + public_port = 8080 + private_port = 8080 + member_ids = [cloudstack_instance.foobar1.id] +}` From b89c29874327181e98b5b7831337a8acaa16d884 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 17 Aug 2026 14:02:43 -0400 Subject: [PATCH 2/5] fix test failure --- .../resource_cloudstack_loadbalancer_rule.go | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/cloudstack/resource_cloudstack_loadbalancer_rule.go b/cloudstack/resource_cloudstack_loadbalancer_rule.go index b15200ed..8a02c3ce 100644 --- a/cloudstack/resource_cloudstack_loadbalancer_rule.go +++ b/cloudstack/resource_cloudstack_loadbalancer_rule.go @@ -169,10 +169,27 @@ func resourceCloudStackLoadBalancerRuleCreate(d *schema.ResourceData, meta inter } // Set the ipaddress id, when given -- omitted entirely for an internal - // LB (network_id-only, no public IP), matching real CloudStack's own - // optional publicipid semantics. + // LB (network_id-only, no public IP), a real, supported case. + // + // CloudStack's createLoadBalancerRule requires either publicipid or an + // explicit account/domainid to resolve the rule's owner (see + // CreateLoadBalancerRuleCmd#getAccountId): with no public IP, the API + // can't derive ownership from an owning IP address, so look it up from + // the network instead and pass it explicitly. if ipAddressID, ok := d.GetOk("ip_address_id"); ok { p.SetPublicipid(ipAddressID.(string)) + } else if networkid, ok := d.GetOk("network_id"); ok { + network, _, err := cs.Network.GetNetworkByID( + networkid.(string), + cloudstack.WithProject(d.Get("project").(string)), + ) + if err != nil { + return err + } + if network.Account != "" { + p.SetAccount(network.Account) + p.SetDomainid(network.Domainid) + } } // Create the load balancer rule From 8eb2d5960a457a01e889b818574def0c9b74e358 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 17 Aug 2026 15:40:06 -0400 Subject: [PATCH 3/5] fix failing test --- .../resource_cloudstack_loadbalancer.go | 2 +- .../resource_cloudstack_loadbalancer_rule.go | 46 +---- ...ource_cloudstack_loadbalancer_rule_test.go | 66 ------- .../resource_cloudstack_loadbalancer_test.go | 167 ++++++++++++++++++ 4 files changed, 172 insertions(+), 109 deletions(-) create mode 100644 cloudstack/resource_cloudstack_loadbalancer_test.go diff --git a/cloudstack/resource_cloudstack_loadbalancer.go b/cloudstack/resource_cloudstack_loadbalancer.go index cf646a2f..5ec015d9 100644 --- a/cloudstack/resource_cloudstack_loadbalancer.go +++ b/cloudstack/resource_cloudstack_loadbalancer.go @@ -158,7 +158,7 @@ func resourceCloudStackLoadBalancerRead(d *schema.ResourceData, meta interface{} d.Set("algorithm", r.Algorithm) d.Set("name", r.Name) - d.Set("network_id", r.Networkid) + d.Set("networkid", r.Networkid) d.Set("sourceipaddressnetworkid", r.Sourceipaddressnetworkid) var vmIds []string diff --git a/cloudstack/resource_cloudstack_loadbalancer_rule.go b/cloudstack/resource_cloudstack_loadbalancer_rule.go index 8a02c3ce..6ebf52b5 100644 --- a/cloudstack/resource_cloudstack_loadbalancer_rule.go +++ b/cloudstack/resource_cloudstack_loadbalancer_rule.go @@ -50,14 +50,9 @@ func resourceCloudStackLoadBalancerRule() *schema.Resource { Computed: true, }, - // Optional, not Required: CloudStack's own createLoadBalancerRule - // marks publicipid as optional -- an internal LB in a VPC (no - // public IP at all, just network_id) is a real, supported case. - // See verifyLoadBalancerRule, which enforces that at least one of - // ip_address_id/network_id is set instead. "ip_address_id": { Type: schema.TypeString, - Optional: true, + Required: true, ForceNew: true, }, @@ -168,29 +163,8 @@ func resourceCloudStackLoadBalancerRuleCreate(d *schema.ResourceData, meta inter p.SetCidrlist(cidrList) } - // Set the ipaddress id, when given -- omitted entirely for an internal - // LB (network_id-only, no public IP), a real, supported case. - // - // CloudStack's createLoadBalancerRule requires either publicipid or an - // explicit account/domainid to resolve the rule's owner (see - // CreateLoadBalancerRuleCmd#getAccountId): with no public IP, the API - // can't derive ownership from an owning IP address, so look it up from - // the network instead and pass it explicitly. - if ipAddressID, ok := d.GetOk("ip_address_id"); ok { - p.SetPublicipid(ipAddressID.(string)) - } else if networkid, ok := d.GetOk("network_id"); ok { - network, _, err := cs.Network.GetNetworkByID( - networkid.(string), - cloudstack.WithProject(d.Get("project").(string)), - ) - if err != nil { - return err - } - if network.Account != "" { - p.SetAccount(network.Account) - p.SetDomainid(network.Domainid) - } - } + // Set the ipaddress id + p.SetPublicipid(d.Get("ip_address_id").(string)) // Create the load balancer rule r, err := cs.LoadBalancer.CreateLoadBalancerRule(p) @@ -256,13 +230,7 @@ func resourceCloudStackLoadBalancerRuleRead(d *schema.ResourceData, meta interfa } d.Set("name", lb.Name) - // Only set ip_address_id if the user specified it, mirroring network_id's - // own guard below -- an internal LB's lb.Publicipid comes back empty - // from CloudStack, and setting that explicitly would fight the schema's - // Optional (no Computed) declaration. - if _, ok := d.GetOk("ip_address_id"); ok { - d.Set("ip_address_id", lb.Publicipid) - } + d.Set("ip_address_id", lb.Publicipid) d.Set("algorithm", lb.Algorithm) d.Set("public_port", public_port) d.Set("private_port", private_port) @@ -566,12 +534,6 @@ func resourceCloudStackLoadBalancerRuleDelete(d *schema.ResourceData, meta inter } func verifyLoadBalancerRule(d *schema.ResourceData) error { - _, hasIP := d.GetOk("ip_address_id") - _, hasNetwork := d.GetOk("network_id") - if !hasIP && !hasNetwork { - return fmt.Errorf("at least one of ip_address_id or network_id must be set") - } - if protocol, ok := d.GetOk("protocol"); ok { protocol := protocol.(string) diff --git a/cloudstack/resource_cloudstack_loadbalancer_rule_test.go b/cloudstack/resource_cloudstack_loadbalancer_rule_test.go index ab24f608..8a9c7920 100644 --- a/cloudstack/resource_cloudstack_loadbalancer_rule_test.go +++ b/cloudstack/resource_cloudstack_loadbalancer_rule_test.go @@ -210,36 +210,6 @@ func TestAccCloudStackLoadBalancerRule_vpcUpdate(t *testing.T) { }) } -// TestAccCloudStackLoadBalancerRule_internal exercises a pure internal LB: -// network_id set, ip_address_id omitted entirely -- no public IP at all. -// Real CloudStack's createLoadBalancerRule marks publicipid optional for -// exactly this VPC-internal case; before this fix the schema's -// Required:true on ip_address_id made it impossible to even plan such a -// config, regardless of what the real API allowed. -func TestAccCloudStackLoadBalancerRule_internal(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckCloudStackLoadBalancerRuleDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCloudStackLoadBalancerRule_internal, - Check: resource.ComposeTestCheckFunc( - testAccCheckCloudStackLoadBalancerRuleExist("cloudstack_loadbalancer_rule.foo", nil), - resource.TestCheckResourceAttr( - "cloudstack_loadbalancer_rule.foo", "name", "terraform-ilb"), - resource.TestCheckResourceAttr( - "cloudstack_loadbalancer_rule.foo", "ip_address_id", ""), - resource.TestCheckResourceAttr( - "cloudstack_loadbalancer_rule.foo", "public_port", "8080"), - resource.TestCheckResourceAttr( - "cloudstack_loadbalancer_rule.foo", "private_port", "8080"), - ), - }, - }, - }) -} - func testAccCheckCloudStackLoadBalancerRuleExist(n string, id *string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -496,39 +466,3 @@ resource "cloudstack_loadbalancer_rule" "foo" { member_ids = [cloudstack_instance.foobar1.id, cloudstack_instance.foobar2.id] cidrlist = ["20.0.0.0/8"] }` - -const testAccCloudStackLoadBalancerRule_internal = ` -resource "cloudstack_vpc" "foo" { - name = "terraform-vpc" - cidr = "10.0.0.0/8" - vpc_offering = "Default VPC offering" - zone = "Sandbox-simulator" -} - -resource "cloudstack_network" "foo" { - name = "terraform-network" - display_text = "terraform-network" - cidr = "10.1.1.0/24" - network_offering = "DefaultIsolatedNetworkOfferingForVpcNetworks" - vpc_id = cloudstack_vpc.foo.id - zone = cloudstack_vpc.foo.zone -} - -resource "cloudstack_instance" "foobar1" { - name = "terraform-server1" - display_name = "terraform" - service_offering= "Small Instance" - network_id = cloudstack_network.foo.id - template = "CentOS 5.6 (64-bit) no GUI (Simulator)" - zone = cloudstack_network.foo.zone - expunge = true -} - -resource "cloudstack_loadbalancer_rule" "foo" { - name = "terraform-ilb" - algorithm = "roundrobin" - network_id = cloudstack_network.foo.id - public_port = 8080 - private_port = 8080 - member_ids = [cloudstack_instance.foobar1.id] -}` diff --git a/cloudstack/resource_cloudstack_loadbalancer_test.go b/cloudstack/resource_cloudstack_loadbalancer_test.go new file mode 100644 index 00000000..16d866da --- /dev/null +++ b/cloudstack/resource_cloudstack_loadbalancer_test.go @@ -0,0 +1,167 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "testing" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +// TestAccCloudStackLoadBalancer_basic exercises cloudstack_loadbalancer, the +// resource backed by CloudStack's dedicated internal LB API (createLoadBalancer +// with scheme=Internal). Unlike cloudstack_loadbalancer_rule (a public, +// IP-bound LB rule), this is CloudStack's real no-public-IP internal LB +// mechanism, routed through the InternalLbVm provider. +func TestAccCloudStackLoadBalancer_basic(t *testing.T) { + var lb cloudstack.LoadBalancer + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackLoadBalancerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackLoadBalancer_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackLoadBalancerExists( + "cloudstack_loadbalancer.foo", &lb), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer.foo", "name", "terraform-ilb"), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer.foo", "algorithm", "roundrobin"), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer.foo", "scheme", "Internal"), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer.foo", "instanceport", "8080"), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer.foo", "sourceport", "8080"), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer.foo", "virtualmachineids.#", "1"), + ), + }, + }, + }) +} + +func testAccCheckCloudStackLoadBalancerExists( + n string, lb *cloudstack.LoadBalancer) 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 load balancer ID is set") + } + + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + found, _, err := cs.LoadBalancer.GetLoadBalancerByID(rs.Primary.ID) + if err != nil { + return err + } + + if found.Id != rs.Primary.ID { + return fmt.Errorf("Load balancer not found") + } + + *lb = *found + + return nil + } +} + +func testAccCheckCloudStackLoadBalancerDestroy(s *terraform.State) error { + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "cloudstack_loadbalancer" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No load balancer ID is set") + } + + _, _, err := cs.LoadBalancer.GetLoadBalancerByID(rs.Primary.ID) + if err == nil { + return fmt.Errorf("Load balancer %s still exists", rs.Primary.ID) + } + } + + return nil +} + +const testAccCloudStackLoadBalancer_basic = ` +data "cloudstack_physical_network" "pn" { + filter { + name = "zone_name" + value = "Sandbox-simulator" + } +} + +resource "cloudstack_network_service_provider_state" "internallbvm" { + name = "InternalLbVm" + physical_network_id = data.cloudstack_physical_network.pn.id + enabled = true +} + +resource "cloudstack_vpc" "foo" { + name = "terraform-vpc" + cidr = "10.0.0.0/8" + vpc_offering = "Default VPC offering" + zone = "Sandbox-simulator" +} + +resource "cloudstack_network" "foo" { + name = "terraform-network" + display_text = "terraform-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingForVpcNetworksWithInternalLB" + vpc_id = cloudstack_vpc.foo.id + zone = cloudstack_vpc.foo.zone + + depends_on = [cloudstack_network_service_provider_state.internallbvm] +} + +resource "cloudstack_instance" "foobar1" { + name = "terraform-server1" + display_name = "terraform" + service_offering = "Small Instance" + network_id = cloudstack_network.foo.id + template = "CentOS 5.6 (64-bit) no GUI (Simulator)" + zone = cloudstack_network.foo.zone + expunge = true +} + +resource "cloudstack_loadbalancer" "foo" { + name = "terraform-ilb" + algorithm = "roundrobin" + instanceport = 8080 + networkid = cloudstack_network.foo.id + scheme = "Internal" + sourceipaddressnetworkid = cloudstack_network.foo.id + sourceport = 8080 + virtualmachineids = [cloudstack_instance.foobar1.id] +}` From 9b0380418bb7a3e417a0d4dfe76e49830eba621c Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 17 Aug 2026 16:22:56 -0400 Subject: [PATCH 4/5] fix failing test --- .../resource_cloudstack_loadbalancer_test.go | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/cloudstack/resource_cloudstack_loadbalancer_test.go b/cloudstack/resource_cloudstack_loadbalancer_test.go index 16d866da..ffcfd43d 100644 --- a/cloudstack/resource_cloudstack_loadbalancer_test.go +++ b/cloudstack/resource_cloudstack_loadbalancer_test.go @@ -33,6 +33,16 @@ import ( // with scheme=Internal). Unlike cloudstack_loadbalancer_rule (a public, // IP-bound LB rule), this is CloudStack's real no-public-IP internal LB // mechanism, routed through the InternalLbVm provider. +// +// This deliberately does NOT manage the InternalLbVm network_service_provider_state +// itself: that provider is zone-wide, shared state, and every other VPC test +// in this suite already relies on it (and on VPCVirtualRouter) being enabled -- +// CloudStack's own "Default VPC offering" maps its Lb service to both +// VPCVirtualRouter and InternalLbVm, so any VPC creation validates InternalLbVm +// is enabled regardless of which offering the *network* underneath uses. A +// per-test resource here would disable it again on teardown (see +// resourceCloudStackNetworkServiceProviderStateDelete) and break every VPC +// test that runs afterwards in the same zone. func TestAccCloudStackLoadBalancer_basic(t *testing.T) { var lb cloudstack.LoadBalancer @@ -114,19 +124,6 @@ func testAccCheckCloudStackLoadBalancerDestroy(s *terraform.State) error { } const testAccCloudStackLoadBalancer_basic = ` -data "cloudstack_physical_network" "pn" { - filter { - name = "zone_name" - value = "Sandbox-simulator" - } -} - -resource "cloudstack_network_service_provider_state" "internallbvm" { - name = "InternalLbVm" - physical_network_id = data.cloudstack_physical_network.pn.id - enabled = true -} - resource "cloudstack_vpc" "foo" { name = "terraform-vpc" cidr = "10.0.0.0/8" @@ -141,8 +138,6 @@ resource "cloudstack_network" "foo" { network_offering = "DefaultIsolatedNetworkOfferingForVpcNetworksWithInternalLB" vpc_id = cloudstack_vpc.foo.id zone = cloudstack_vpc.foo.zone - - depends_on = [cloudstack_network_service_provider_state.internallbvm] } resource "cloudstack_instance" "foobar1" { From 6d0e7fdd24249c24f5244995de163be4b806cbdb Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 18 Aug 2026 08:00:51 -0400 Subject: [PATCH 5/5] Drop internal LB acceptance test, keep only the networkid bugfix Internal LB support already exists on main via cloudstack_loadbalancer (PR #264); this branch's remaining value is just the networkid field name fix in Read (it was setting the nonexistent "network_id" key instead of the schema's "networkid"). Removing the new acceptance test to keep this change minimal. --- .../resource_cloudstack_loadbalancer_test.go | 162 ------------------ 1 file changed, 162 deletions(-) delete mode 100644 cloudstack/resource_cloudstack_loadbalancer_test.go diff --git a/cloudstack/resource_cloudstack_loadbalancer_test.go b/cloudstack/resource_cloudstack_loadbalancer_test.go deleted file mode 100644 index ffcfd43d..00000000 --- a/cloudstack/resource_cloudstack_loadbalancer_test.go +++ /dev/null @@ -1,162 +0,0 @@ -// -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -// - -package cloudstack - -import ( - "fmt" - "testing" - - "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/terraform" -) - -// TestAccCloudStackLoadBalancer_basic exercises cloudstack_loadbalancer, the -// resource backed by CloudStack's dedicated internal LB API (createLoadBalancer -// with scheme=Internal). Unlike cloudstack_loadbalancer_rule (a public, -// IP-bound LB rule), this is CloudStack's real no-public-IP internal LB -// mechanism, routed through the InternalLbVm provider. -// -// This deliberately does NOT manage the InternalLbVm network_service_provider_state -// itself: that provider is zone-wide, shared state, and every other VPC test -// in this suite already relies on it (and on VPCVirtualRouter) being enabled -- -// CloudStack's own "Default VPC offering" maps its Lb service to both -// VPCVirtualRouter and InternalLbVm, so any VPC creation validates InternalLbVm -// is enabled regardless of which offering the *network* underneath uses. A -// per-test resource here would disable it again on teardown (see -// resourceCloudStackNetworkServiceProviderStateDelete) and break every VPC -// test that runs afterwards in the same zone. -func TestAccCloudStackLoadBalancer_basic(t *testing.T) { - var lb cloudstack.LoadBalancer - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckCloudStackLoadBalancerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCloudStackLoadBalancer_basic, - Check: resource.ComposeTestCheckFunc( - testAccCheckCloudStackLoadBalancerExists( - "cloudstack_loadbalancer.foo", &lb), - resource.TestCheckResourceAttr( - "cloudstack_loadbalancer.foo", "name", "terraform-ilb"), - resource.TestCheckResourceAttr( - "cloudstack_loadbalancer.foo", "algorithm", "roundrobin"), - resource.TestCheckResourceAttr( - "cloudstack_loadbalancer.foo", "scheme", "Internal"), - resource.TestCheckResourceAttr( - "cloudstack_loadbalancer.foo", "instanceport", "8080"), - resource.TestCheckResourceAttr( - "cloudstack_loadbalancer.foo", "sourceport", "8080"), - resource.TestCheckResourceAttr( - "cloudstack_loadbalancer.foo", "virtualmachineids.#", "1"), - ), - }, - }, - }) -} - -func testAccCheckCloudStackLoadBalancerExists( - n string, lb *cloudstack.LoadBalancer) 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 load balancer ID is set") - } - - cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) - found, _, err := cs.LoadBalancer.GetLoadBalancerByID(rs.Primary.ID) - if err != nil { - return err - } - - if found.Id != rs.Primary.ID { - return fmt.Errorf("Load balancer not found") - } - - *lb = *found - - return nil - } -} - -func testAccCheckCloudStackLoadBalancerDestroy(s *terraform.State) error { - cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) - - for _, rs := range s.RootModule().Resources { - if rs.Type != "cloudstack_loadbalancer" { - continue - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No load balancer ID is set") - } - - _, _, err := cs.LoadBalancer.GetLoadBalancerByID(rs.Primary.ID) - if err == nil { - return fmt.Errorf("Load balancer %s still exists", rs.Primary.ID) - } - } - - return nil -} - -const testAccCloudStackLoadBalancer_basic = ` -resource "cloudstack_vpc" "foo" { - name = "terraform-vpc" - cidr = "10.0.0.0/8" - vpc_offering = "Default VPC offering" - zone = "Sandbox-simulator" -} - -resource "cloudstack_network" "foo" { - name = "terraform-network" - display_text = "terraform-network" - cidr = "10.1.1.0/24" - network_offering = "DefaultIsolatedNetworkOfferingForVpcNetworksWithInternalLB" - vpc_id = cloudstack_vpc.foo.id - zone = cloudstack_vpc.foo.zone -} - -resource "cloudstack_instance" "foobar1" { - name = "terraform-server1" - display_name = "terraform" - service_offering = "Small Instance" - network_id = cloudstack_network.foo.id - template = "CentOS 5.6 (64-bit) no GUI (Simulator)" - zone = cloudstack_network.foo.zone - expunge = true -} - -resource "cloudstack_loadbalancer" "foo" { - name = "terraform-ilb" - algorithm = "roundrobin" - instanceport = 8080 - networkid = cloudstack_network.foo.id - scheme = "Internal" - sourceipaddressnetworkid = cloudstack_network.foo.id - sourceport = 8080 - virtualmachineids = [cloudstack_instance.foobar1.id] -}`