Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cloudstack/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func Provider() *schema.Provider {
"cloudstack_firewall": resourceCloudStackFirewall(),
"cloudstack_host": resourceCloudStackHost(),
"cloudstack_instance": resourceCloudStackInstance(),
"cloudstack_internal_loadbalancer": resourceCloudStackInternalLoadBalancer(),
"cloudstack_ipaddress": resourceCloudStackIPAddress(),
"cloudstack_kubernetes_cluster": resourceCloudStackKubernetesCluster(),
"cloudstack_kubernetes_version": resourceCloudStackKubernetesVersion(),
Expand Down
238 changes: 238 additions & 0 deletions cloudstack/resource_cloudstack_internal_loadbalancer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
//
// 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"
"log"

"github.com/apache/cloudstack-go/v2/cloudstack"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

// resourceCloudStackInternalLoadBalancer manages an Internal LB rule on a VPC
// tier. Unlike cloudstack_loadbalancer_rule (a PUBLIC rule created via
// createLoadBalancerRule), the Internal LB uses createLoadBalancer with
// scheme=Internal: create / read / update (members) / delete / import.
func resourceCloudStackInternalLoadBalancer() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackInternalLoadBalancerCreate,
Read: resourceCloudStackInternalLoadBalancerRead,
Update: resourceCloudStackInternalLoadBalancerUpdate,
Delete: resourceCloudStackInternalLoadBalancerDelete,

Importer: &schema.ResourceImporter{
State: importStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"description": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},

"algorithm": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"source_port": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},

"instance_port": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},

// The guest network the internal LB serves.
"network_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

// Network for the source IP; defaults to network_id when omitted.
"source_network_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},

"source_ip": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},

"member_ids": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
}
}

func ilbMemberIds(d *schema.ResourceData) []string {
set := d.Get("member_ids").(*schema.Set)
ids := make([]string, 0, set.Len())
for _, id := range set.List() {
ids = append(ids, id.(string))
}
return ids
}

func resourceCloudStackInternalLoadBalancerCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)

networkid := d.Get("network_id").(string)
sourceNetwork := networkid
if v, ok := d.GetOk("source_network_id"); ok && v.(string) != "" {
sourceNetwork = v.(string)
}

p := cs.LoadBalancer.NewCreateLoadBalancerParams(
d.Get("algorithm").(string),
d.Get("instance_port").(int),
d.Get("name").(string),
networkid,
"Internal",
sourceNetwork,
d.Get("source_port").(int),
)
if v, ok := d.GetOk("description"); ok {
p.SetDescription(v.(string))
}
if v, ok := d.GetOk("source_ip"); ok && v.(string) != "" {
p.SetSourceipaddress(v.(string))
}

r, err := cs.LoadBalancer.CreateLoadBalancer(p)
if err != nil {
return fmt.Errorf("Error creating internal load balancer %s: %s", d.Get("name").(string), err)
}
d.SetId(r.Id)

if ids := ilbMemberIds(d); len(ids) > 0 {
ap := cs.LoadBalancer.NewAssignToLoadBalancerRuleParams(r.Id)
ap.SetVirtualmachineids(ids)
if _, err := cs.LoadBalancer.AssignToLoadBalancerRule(ap); err != nil {
return fmt.Errorf("Error assigning instances to internal LB %s: %s", r.Id, err)
}
}

return resourceCloudStackInternalLoadBalancerRead(d, meta)
}

func resourceCloudStackInternalLoadBalancerRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)

lb, count, err := cs.LoadBalancer.GetLoadBalancerByID(d.Id())
if err != nil {
if count == 0 {
log.Printf("[DEBUG] Internal LB with ID %s no longer exists", d.Id())
d.SetId("")
return nil
}
return err
}

d.Set("name", lb.Name)
d.Set("algorithm", lb.Algorithm)
d.Set("description", lb.Description)
d.Set("network_id", lb.Networkid)
d.Set("source_network_id", lb.Sourceipaddressnetworkid)
d.Set("source_ip", lb.Sourceipaddress)

members := make([]string, 0, len(lb.Loadbalancerinstance))
for _, m := range lb.Loadbalancerinstance {
members = append(members, m.Id)
}
d.Set("member_ids", members)

// NOTE: the listLoadBalancers response does not return source/instance ports,
// so they keep their configured values (both are ForceNew anyway).
return nil
}

func resourceCloudStackInternalLoadBalancerUpdate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)

if d.HasChange("member_ids") {
o, n := d.GetChange("member_ids")
oldSet := o.(*schema.Set)
newSet := n.(*schema.Set)

toAdd := toStringList(newSet.Difference(oldSet).List())
toRemove := toStringList(oldSet.Difference(newSet).List())

if len(toRemove) > 0 {
rp := cs.LoadBalancer.NewRemoveFromLoadBalancerRuleParams(d.Id())
rp.SetVirtualmachineids(toRemove)
if _, err := cs.LoadBalancer.RemoveFromLoadBalancerRule(rp); err != nil {
return fmt.Errorf("Error removing instances from internal LB %s: %s", d.Id(), err)
}
}
if len(toAdd) > 0 {
ap := cs.LoadBalancer.NewAssignToLoadBalancerRuleParams(d.Id())
ap.SetVirtualmachineids(toAdd)
if _, err := cs.LoadBalancer.AssignToLoadBalancerRule(ap); err != nil {
return fmt.Errorf("Error assigning instances to internal LB %s: %s", d.Id(), err)
}
}
}

return resourceCloudStackInternalLoadBalancerRead(d, meta)
}

func resourceCloudStackInternalLoadBalancerDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)

p := cs.LoadBalancer.NewDeleteLoadBalancerParams(d.Id())
if _, err := cs.LoadBalancer.DeleteLoadBalancer(p); err != nil {
// Ignore the error if the internal LB is already gone.
log.Printf("[DEBUG] Error deleting internal LB %s: %s", d.Id(), err)
return err
}
return nil
}

func toStringList(in []interface{}) []string {
out := make([]string, 0, len(in))
for _, v := range in {
out = append(out, v.(string))
}
return out
}
74 changes: 74 additions & 0 deletions website/docs/r/internal_loadbalancer.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_internal_loadbalancer"
sidebar_current: "docs-cloudstack-resource-internal-loadbalancer"
description: |-
Creates an internal load balancer rule.
---

# cloudstack_internal_loadbalancer

Creates an internal load balancer rule (`createLoadBalancer` with
`scheme = Internal`). An internal load balancer distributes traffic to a set of
instances within a VPC tier, reached through a private source IP on that tier's
network rather than a public IP.

## Example Usage

```hcl
resource "cloudstack_internal_loadbalancer" "app" {
name = "app-ilb"
algorithm = "roundrobin"
source_port = 80
instance_port = 8080
network_id = cloudstack_network.app_tier.id
member_ids = cloudstack_instance.app.*.id
# source_ip is optional — CloudStack auto-assigns one from the tier when omitted.
}
```

## Argument Reference

The following arguments are supported:

* `name` - (Required) The name of the internal load balancer rule.

* `description` - (Optional) The description of the internal load balancer rule.

* `algorithm` - (Required) The load balancing algorithm. Valid values are
`source`, `roundrobin` and `leastconn`.

* `source_port` - (Required) The source (front-end) port the rule listens on.

* `instance_port` - (Required) The instance (back-end) port traffic is forwarded
to.

* `network_id` - (Required) The ID of the network (VPC tier) the internal load
balancer is created on. Changing this forces a new resource to be created.

* `source_network_id` - (Optional) The ID of the network the source IP is taken
from. Defaults to `network_id`. Changing this forces a new resource to be
created.

* `source_ip` - (Optional) The private source IP the rule listens on. When
omitted CloudStack assigns one automatically. Changing this forces a new
resource to be created.

* `member_ids` - (Optional) A set of instance IDs assigned to (load balanced by)
this rule.

## Attributes Reference

The following attributes are exported:

* `id` - The id of the internal load balancer rule.
* `source_ip` - The private source IP the rule listens on (computed when not set).

## Import

Internal load balancer rules can be imported; use `<INTERNAL LB RULE ID>` as the
import ID. For example:

```shell
terraform import cloudstack_internal_loadbalancer.app 6226ea4d-9cbe-4cc9-b30c-b9532146da5b
```
Loading