607 lines
18 KiB
Go
607 lines
18 KiB
Go
package alicloud
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"errors"
|
|
"github.com/denverdino/aliyungo/common"
|
|
"github.com/denverdino/aliyungo/slb"
|
|
"github.com/hashicorp/terraform/helper/hashcode"
|
|
"github.com/hashicorp/terraform/helper/resource"
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
"time"
|
|
)
|
|
|
|
func resourceAliyunSlb() *schema.Resource {
|
|
return &schema.Resource{
|
|
Create: resourceAliyunSlbCreate,
|
|
Read: resourceAliyunSlbRead,
|
|
Update: resourceAliyunSlbUpdate,
|
|
Delete: resourceAliyunSlbDelete,
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
"name": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ValidateFunc: validateSlbName,
|
|
Computed: true,
|
|
},
|
|
|
|
"internet": &schema.Schema{
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"vswitch_id": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"internet_charge_type": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Default: "paybytraffic",
|
|
ValidateFunc: validateSlbInternetChargeType,
|
|
},
|
|
|
|
"bandwidth": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
ValidateFunc: validateSlbBandwidth,
|
|
Computed: true,
|
|
},
|
|
|
|
"listener": &schema.Schema{
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"instance_port": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
ValidateFunc: validateInstancePort,
|
|
Required: true,
|
|
},
|
|
|
|
"lb_port": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
ValidateFunc: validateInstancePort,
|
|
Required: true,
|
|
},
|
|
|
|
"lb_protocol": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
ValidateFunc: validateInstanceProtocol,
|
|
Required: true,
|
|
},
|
|
|
|
"bandwidth": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
ValidateFunc: validateSlbListenerBandwidth,
|
|
Required: true,
|
|
},
|
|
"scheduler": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
ValidateFunc: validateSlbListenerScheduler,
|
|
Optional: true,
|
|
Default: slb.WRRScheduler,
|
|
},
|
|
//http & https
|
|
"sticky_session": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
ValidateFunc: validateAllowedStringValue([]string{
|
|
string(slb.OnFlag),
|
|
string(slb.OffFlag)}),
|
|
Optional: true,
|
|
Default: slb.OffFlag,
|
|
},
|
|
//http & https
|
|
"sticky_session_type": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
ValidateFunc: validateAllowedStringValue([]string{
|
|
string(slb.InsertStickySessionType),
|
|
string(slb.ServerStickySessionType)}),
|
|
Optional: true,
|
|
},
|
|
//http & https
|
|
"cookie_timeout": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
ValidateFunc: validateSlbListenerCookieTimeout,
|
|
Optional: true,
|
|
},
|
|
//http & https
|
|
"cookie": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
ValidateFunc: validateSlbListenerCookie,
|
|
Optional: true,
|
|
},
|
|
//tcp & udp
|
|
"persistence_timeout": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
ValidateFunc: validateSlbListenerPersistenceTimeout,
|
|
Optional: true,
|
|
Default: 0,
|
|
},
|
|
//http & https
|
|
"health_check": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
ValidateFunc: validateAllowedStringValue([]string{
|
|
string(slb.OnFlag),
|
|
string(slb.OffFlag)}),
|
|
Optional: true,
|
|
Default: slb.OffFlag,
|
|
},
|
|
//tcp
|
|
"health_check_type": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
ValidateFunc: validateAllowedStringValue([]string{
|
|
string(slb.TCPHealthCheckType),
|
|
string(slb.HTTPHealthCheckType)}),
|
|
Optional: true,
|
|
Default: slb.TCPHealthCheckType,
|
|
},
|
|
//http & https & tcp
|
|
"health_check_domain": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
ValidateFunc: validateSlbListenerHealthCheckDomain,
|
|
Optional: true,
|
|
},
|
|
//http & https & tcp
|
|
"health_check_uri": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
ValidateFunc: validateSlbListenerHealthCheckUri,
|
|
Optional: true,
|
|
},
|
|
"health_check_connect_port": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
ValidateFunc: validateSlbListenerHealthCheckConnectPort,
|
|
Optional: true,
|
|
},
|
|
"healthy_threshold": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
ValidateFunc: validateIntegerInRange(1, 10),
|
|
Optional: true,
|
|
},
|
|
"unhealthy_threshold": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
ValidateFunc: validateIntegerInRange(1, 10),
|
|
Optional: true,
|
|
},
|
|
|
|
"health_check_timeout": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
ValidateFunc: validateIntegerInRange(1, 50),
|
|
Optional: true,
|
|
},
|
|
"health_check_interval": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
ValidateFunc: validateIntegerInRange(1, 5),
|
|
Optional: true,
|
|
},
|
|
//http & https & tcp
|
|
"health_check_http_code": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
ValidateFunc: validateAllowedSplitStringValue([]string{
|
|
string(slb.HTTP_2XX),
|
|
string(slb.HTTP_3XX),
|
|
string(slb.HTTP_4XX),
|
|
string(slb.HTTP_5XX)}, ","),
|
|
Optional: true,
|
|
},
|
|
//https
|
|
"ssl_certificate_id": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
},
|
|
//https
|
|
//"ca_certificate_id": &schema.Schema{
|
|
// Type: schema.TypeString,
|
|
// Optional: true,
|
|
//},
|
|
},
|
|
},
|
|
Set: resourceAliyunSlbListenerHash,
|
|
},
|
|
|
|
//deprecated
|
|
"instances": &schema.Schema{
|
|
Type: schema.TypeSet,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Optional: true,
|
|
Set: schema.HashString,
|
|
},
|
|
|
|
"address": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func resourceAliyunSlbCreate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
slbconn := meta.(*AliyunClient).slbconn
|
|
|
|
var slbName string
|
|
if v, ok := d.GetOk("name"); ok {
|
|
slbName = v.(string)
|
|
} else {
|
|
slbName = resource.PrefixedUniqueId("tf-lb-")
|
|
d.Set("name", slbName)
|
|
}
|
|
|
|
slbArgs := &slb.CreateLoadBalancerArgs{
|
|
RegionId: getRegion(d, meta),
|
|
LoadBalancerName: slbName,
|
|
}
|
|
|
|
if internet, ok := d.GetOk("internet"); ok && internet.(bool) {
|
|
slbArgs.AddressType = slb.InternetAddressType
|
|
d.Set("internet", true)
|
|
} else {
|
|
slbArgs.AddressType = slb.IntranetAddressType
|
|
d.Set("internet", false)
|
|
}
|
|
|
|
if v, ok := d.GetOk("internet_charge_type"); ok && v.(string) != "" {
|
|
slbArgs.InternetChargeType = slb.InternetChargeType(v.(string))
|
|
}
|
|
|
|
if v, ok := d.GetOk("bandwidth"); ok && v.(int) != 0 {
|
|
slbArgs.Bandwidth = v.(int)
|
|
}
|
|
|
|
if v, ok := d.GetOk("vswitch_id"); ok && v.(string) != "" {
|
|
slbArgs.VSwitchId = v.(string)
|
|
}
|
|
slb, err := slbconn.CreateLoadBalancer(slbArgs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.SetId(slb.LoadBalancerId)
|
|
|
|
return resourceAliyunSlbUpdate(d, meta)
|
|
}
|
|
|
|
func resourceAliyunSlbRead(d *schema.ResourceData, meta interface{}) error {
|
|
slbconn := meta.(*AliyunClient).slbconn
|
|
loadBalancer, err := slbconn.DescribeLoadBalancerAttribute(d.Id())
|
|
if err != nil {
|
|
if notFoundError(err) {
|
|
d.SetId("")
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
d.Set("name", loadBalancer.LoadBalancerName)
|
|
|
|
if loadBalancer.AddressType == slb.InternetAddressType {
|
|
d.Set("internal", true)
|
|
} else {
|
|
d.Set("internal", false)
|
|
}
|
|
d.Set("internet_charge_type", loadBalancer.InternetChargeType)
|
|
d.Set("bandwidth", loadBalancer.Bandwidth)
|
|
d.Set("vswitch_id", loadBalancer.VSwitchId)
|
|
d.Set("address", loadBalancer.Address)
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceAliyunSlbUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
slbconn := meta.(*AliyunClient).slbconn
|
|
|
|
d.Partial(true)
|
|
|
|
if d.HasChange("name") {
|
|
err := slbconn.SetLoadBalancerName(d.Id(), d.Get("name").(string))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.SetPartial("name")
|
|
}
|
|
|
|
if d.Get("internet") == true && d.Get("internet_charge_type") == "paybybandwidth" {
|
|
//don't intranet web and paybybandwidth, then can modify bandwidth
|
|
if d.HasChange("bandwidth") {
|
|
args := &slb.ModifyLoadBalancerInternetSpecArgs{
|
|
LoadBalancerId: d.Id(),
|
|
Bandwidth: d.Get("bandwidth").(int),
|
|
}
|
|
err := slbconn.ModifyLoadBalancerInternetSpec(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.SetPartial("bandwidth")
|
|
}
|
|
}
|
|
|
|
if d.HasChange("listener") {
|
|
o, n := d.GetChange("listener")
|
|
os := o.(*schema.Set)
|
|
ns := n.(*schema.Set)
|
|
|
|
remove, _ := expandListeners(os.Difference(ns).List())
|
|
add, _ := expandListeners(ns.Difference(os).List())
|
|
|
|
if len(remove) > 0 {
|
|
for _, listener := range remove {
|
|
err := slbconn.DeleteLoadBalancerListener(d.Id(), listener.LoadBalancerPort)
|
|
if err != nil {
|
|
return fmt.Errorf("Failure removing outdated SLB listeners: %#v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(add) > 0 {
|
|
for _, listener := range add {
|
|
err := createListener(slbconn, d.Id(), listener)
|
|
if err != nil {
|
|
return fmt.Errorf("Failure add SLB listeners: %#v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
d.SetPartial("listener")
|
|
}
|
|
|
|
// If we currently have instances, or did have instances,
|
|
// we want to figure out what to add and remove from the load
|
|
// balancer
|
|
if d.HasChange("instances") {
|
|
o, n := d.GetChange("instances")
|
|
os := o.(*schema.Set)
|
|
ns := n.(*schema.Set)
|
|
remove := expandBackendServers(os.Difference(ns).List())
|
|
add := expandBackendServers(ns.Difference(os).List())
|
|
|
|
if len(add) > 0 {
|
|
_, err := slbconn.AddBackendServers(d.Id(), add)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if len(remove) > 0 {
|
|
removeBackendServers := make([]string, 0, len(remove))
|
|
for _, e := range remove {
|
|
removeBackendServers = append(removeBackendServers, e.ServerId)
|
|
}
|
|
_, err := slbconn.RemoveBackendServers(d.Id(), removeBackendServers)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d.SetPartial("instances")
|
|
}
|
|
|
|
d.Partial(false)
|
|
|
|
return resourceAliyunSlbRead(d, meta)
|
|
}
|
|
|
|
func resourceAliyunSlbDelete(d *schema.ResourceData, meta interface{}) error {
|
|
conn := meta.(*AliyunClient).slbconn
|
|
|
|
return resource.Retry(5*time.Minute, func() *resource.RetryError {
|
|
err := conn.DeleteLoadBalancer(d.Id())
|
|
|
|
if err != nil {
|
|
return resource.NonRetryableError(err)
|
|
}
|
|
|
|
loadBalancer, err := conn.DescribeLoadBalancerAttribute(d.Id())
|
|
if err != nil {
|
|
e, _ := err.(*common.Error)
|
|
if e.ErrorResponse.Code == LoadBalancerNotFound {
|
|
return nil
|
|
}
|
|
return resource.NonRetryableError(err)
|
|
}
|
|
if loadBalancer != nil {
|
|
return resource.RetryableError(fmt.Errorf("LoadBalancer in use - trying again while it deleted."))
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func resourceAliyunSlbListenerHash(v interface{}) int {
|
|
var buf bytes.Buffer
|
|
m := v.(map[string]interface{})
|
|
buf.WriteString(fmt.Sprintf("%d-", m["instance_port"].(int)))
|
|
buf.WriteString(fmt.Sprintf("%d-", m["lb_port"].(int)))
|
|
buf.WriteString(fmt.Sprintf("%s-",
|
|
strings.ToLower(m["lb_protocol"].(string))))
|
|
|
|
buf.WriteString(fmt.Sprintf("%d-", m["bandwidth"].(int)))
|
|
|
|
if v, ok := m["ssl_certificate_id"]; ok {
|
|
buf.WriteString(fmt.Sprintf("%s-", v.(string)))
|
|
}
|
|
|
|
return hashcode.String(buf.String())
|
|
}
|
|
|
|
func createListener(conn *slb.Client, loadBalancerId string, listener *Listener) error {
|
|
|
|
errTypeJudge := func(err error) error {
|
|
if err != nil {
|
|
if listenerType, ok := err.(*ListenerErr); ok {
|
|
if listenerType.ErrType == HealthCheckErrType {
|
|
return fmt.Errorf("When the HealthCheck is %s, then related HealthCheck parameter "+
|
|
"must have.", slb.OnFlag)
|
|
} else if listenerType.ErrType == StickySessionErrType {
|
|
return fmt.Errorf("When the StickySession is %s, then StickySessionType parameter "+
|
|
"must have.", slb.OnFlag)
|
|
} else if listenerType.ErrType == CookieTimeOutErrType {
|
|
return fmt.Errorf("When the StickySession is %s and StickySessionType is %s, "+
|
|
"then CookieTimeout parameter must have.", slb.OnFlag, slb.InsertStickySessionType)
|
|
} else if listenerType.ErrType == CookieErrType {
|
|
return fmt.Errorf("When the StickySession is %s and StickySessionType is %s, "+
|
|
"then Cookie parameter must have.", slb.OnFlag, slb.ServerStickySessionType)
|
|
}
|
|
return fmt.Errorf("slb listener check errtype not found.")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if listener.Protocol == strings.ToLower("tcp") {
|
|
|
|
args := getTcpListenerArgs(loadBalancerId, listener)
|
|
|
|
if err := conn.CreateLoadBalancerTCPListener(&args); err != nil {
|
|
return err
|
|
}
|
|
} else if listener.Protocol == strings.ToLower("http") {
|
|
args, argsErr := getHttpListenerArgs(loadBalancerId, listener)
|
|
if paramErr := errTypeJudge(argsErr); paramErr != nil {
|
|
return paramErr
|
|
}
|
|
|
|
if err := conn.CreateLoadBalancerHTTPListener(&args); err != nil {
|
|
return err
|
|
}
|
|
} else if listener.Protocol == strings.ToLower("https") {
|
|
listenerType, err := getHttpListenerType(loadBalancerId, listener)
|
|
if paramErr := errTypeJudge(err); paramErr != nil {
|
|
return paramErr
|
|
}
|
|
|
|
args := &slb.CreateLoadBalancerHTTPSListenerArgs{
|
|
HTTPListenerType: listenerType,
|
|
}
|
|
if listener.SSLCertificateId == "" {
|
|
return fmt.Errorf("Server Certificated Id cann't be null")
|
|
}
|
|
|
|
args.ServerCertificateId = listener.SSLCertificateId
|
|
|
|
if err := conn.CreateLoadBalancerHTTPSListener(args); err != nil {
|
|
return err
|
|
}
|
|
} else if listener.Protocol == strings.ToLower("udp") {
|
|
args := getUdpListenerArgs(loadBalancerId, listener)
|
|
|
|
if err := conn.CreateLoadBalancerUDPListener(&args); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := conn.StartLoadBalancerListener(loadBalancerId, listener.LoadBalancerPort); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getTcpListenerArgs(loadBalancerId string, listener *Listener) slb.CreateLoadBalancerTCPListenerArgs {
|
|
args := slb.CreateLoadBalancerTCPListenerArgs{
|
|
LoadBalancerId: loadBalancerId,
|
|
ListenerPort: listener.LoadBalancerPort,
|
|
BackendServerPort: listener.InstancePort,
|
|
Bandwidth: listener.Bandwidth,
|
|
Scheduler: listener.Scheduler,
|
|
PersistenceTimeout: listener.PersistenceTimeout,
|
|
HealthCheckType: listener.HealthCheckType,
|
|
HealthCheckDomain: listener.HealthCheckDomain,
|
|
HealthCheckURI: listener.HealthCheckURI,
|
|
HealthCheckConnectPort: listener.HealthCheckConnectPort,
|
|
HealthyThreshold: listener.HealthyThreshold,
|
|
UnhealthyThreshold: listener.UnhealthyThreshold,
|
|
HealthCheckConnectTimeout: listener.HealthCheckTimeout,
|
|
HealthCheckInterval: listener.HealthCheckInterval,
|
|
HealthCheckHttpCode: listener.HealthCheckHttpCode,
|
|
}
|
|
return args
|
|
}
|
|
|
|
func getUdpListenerArgs(loadBalancerId string, listener *Listener) slb.CreateLoadBalancerUDPListenerArgs {
|
|
args := slb.CreateLoadBalancerUDPListenerArgs{
|
|
LoadBalancerId: loadBalancerId,
|
|
ListenerPort: listener.LoadBalancerPort,
|
|
BackendServerPort: listener.InstancePort,
|
|
Bandwidth: listener.Bandwidth,
|
|
PersistenceTimeout: listener.PersistenceTimeout,
|
|
HealthCheckConnectTimeout: listener.HealthCheckTimeout,
|
|
HealthCheckInterval: listener.HealthCheckInterval,
|
|
}
|
|
return args
|
|
}
|
|
|
|
func getHttpListenerType(loadBalancerId string, listener *Listener) (listenType slb.HTTPListenerType, err error) {
|
|
|
|
if listener.HealthCheck == slb.OnFlag {
|
|
if listener.HealthCheckURI == "" || listener.HealthCheckDomain == "" || listener.HealthCheckConnectPort == 0 ||
|
|
listener.HealthyThreshold == 0 || listener.UnhealthyThreshold == 0 || listener.HealthCheckTimeout == 0 ||
|
|
listener.HealthCheckHttpCode == "" || listener.HealthCheckInterval == 0 {
|
|
|
|
errMsg := errors.New("err: HealthCheck empty.")
|
|
return listenType, &ListenerErr{HealthCheckErrType, errMsg}
|
|
}
|
|
}
|
|
|
|
if listener.StickySession == slb.OnFlag {
|
|
if listener.StickySessionType == "" {
|
|
errMsg := errors.New("err: stickySession empty.")
|
|
return listenType, &ListenerErr{StickySessionErrType, errMsg}
|
|
}
|
|
|
|
if listener.StickySessionType == slb.InsertStickySessionType {
|
|
if listener.CookieTimeout == 0 {
|
|
errMsg := errors.New("err: cookieTimeout empty.")
|
|
return listenType, &ListenerErr{CookieTimeOutErrType, errMsg}
|
|
}
|
|
} else if listener.StickySessionType == slb.ServerStickySessionType {
|
|
if listener.Cookie == "" {
|
|
errMsg := errors.New("err: cookie empty.")
|
|
return listenType, &ListenerErr{CookieErrType, errMsg}
|
|
}
|
|
}
|
|
}
|
|
|
|
httpListenertType := slb.HTTPListenerType{
|
|
LoadBalancerId: loadBalancerId,
|
|
ListenerPort: listener.LoadBalancerPort,
|
|
BackendServerPort: listener.InstancePort,
|
|
Bandwidth: listener.Bandwidth,
|
|
Scheduler: listener.Scheduler,
|
|
HealthCheck: listener.HealthCheck,
|
|
StickySession: listener.StickySession,
|
|
StickySessionType: listener.StickySessionType,
|
|
CookieTimeout: listener.CookieTimeout,
|
|
Cookie: listener.Cookie,
|
|
HealthCheckDomain: listener.HealthCheckDomain,
|
|
HealthCheckURI: listener.HealthCheckURI,
|
|
HealthCheckConnectPort: listener.HealthCheckConnectPort,
|
|
HealthyThreshold: listener.HealthyThreshold,
|
|
UnhealthyThreshold: listener.UnhealthyThreshold,
|
|
HealthCheckTimeout: listener.HealthCheckTimeout,
|
|
HealthCheckInterval: listener.HealthCheckInterval,
|
|
HealthCheckHttpCode: listener.HealthCheckHttpCode,
|
|
}
|
|
|
|
return httpListenertType, err
|
|
}
|
|
|
|
func getHttpListenerArgs(loadBalancerId string, listener *Listener) (listenType slb.CreateLoadBalancerHTTPListenerArgs, err error) {
|
|
httpListenerType, err := getHttpListenerType(loadBalancerId, listener)
|
|
if err != nil {
|
|
return listenType, err
|
|
}
|
|
|
|
httpArgs := slb.CreateLoadBalancerHTTPListenerArgs(httpListenerType)
|
|
return httpArgs, err
|
|
}
|