github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/builtin/providers/alicloud/resource_alicloud_slb.go (about) 1 package alicloud 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 8 "errors" 9 "github.com/denverdino/aliyungo/common" 10 "github.com/denverdino/aliyungo/slb" 11 "github.com/hashicorp/terraform/helper/hashcode" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 "time" 15 ) 16 17 func resourceAliyunSlb() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAliyunSlbCreate, 20 Read: resourceAliyunSlbRead, 21 Update: resourceAliyunSlbUpdate, 22 Delete: resourceAliyunSlbDelete, 23 24 Schema: map[string]*schema.Schema{ 25 "name": &schema.Schema{ 26 Type: schema.TypeString, 27 Optional: true, 28 ValidateFunc: validateSlbName, 29 Computed: true, 30 }, 31 32 "internet": &schema.Schema{ 33 Type: schema.TypeBool, 34 Optional: true, 35 ForceNew: true, 36 }, 37 38 "vswitch_id": &schema.Schema{ 39 Type: schema.TypeString, 40 Optional: true, 41 ForceNew: true, 42 }, 43 44 "internet_charge_type": &schema.Schema{ 45 Type: schema.TypeString, 46 Optional: true, 47 ForceNew: true, 48 Default: "paybytraffic", 49 ValidateFunc: validateSlbInternetChargeType, 50 }, 51 52 "bandwidth": &schema.Schema{ 53 Type: schema.TypeInt, 54 Optional: true, 55 ValidateFunc: validateSlbBandwidth, 56 Computed: true, 57 }, 58 59 "listener": &schema.Schema{ 60 Type: schema.TypeSet, 61 Optional: true, 62 Elem: &schema.Resource{ 63 Schema: map[string]*schema.Schema{ 64 "instance_port": &schema.Schema{ 65 Type: schema.TypeInt, 66 ValidateFunc: validateInstancePort, 67 Required: true, 68 }, 69 70 "lb_port": &schema.Schema{ 71 Type: schema.TypeInt, 72 ValidateFunc: validateInstancePort, 73 Required: true, 74 }, 75 76 "lb_protocol": &schema.Schema{ 77 Type: schema.TypeString, 78 ValidateFunc: validateInstanceProtocol, 79 Required: true, 80 }, 81 82 "bandwidth": &schema.Schema{ 83 Type: schema.TypeInt, 84 ValidateFunc: validateSlbListenerBandwidth, 85 Required: true, 86 }, 87 "scheduler": &schema.Schema{ 88 Type: schema.TypeString, 89 ValidateFunc: validateSlbListenerScheduler, 90 Optional: true, 91 Default: slb.WRRScheduler, 92 }, 93 //http & https 94 "sticky_session": &schema.Schema{ 95 Type: schema.TypeString, 96 ValidateFunc: validateAllowedStringValue([]string{ 97 string(slb.OnFlag), 98 string(slb.OffFlag)}), 99 Optional: true, 100 Default: slb.OffFlag, 101 }, 102 //http & https 103 "sticky_session_type": &schema.Schema{ 104 Type: schema.TypeString, 105 ValidateFunc: validateAllowedStringValue([]string{ 106 string(slb.InsertStickySessionType), 107 string(slb.ServerStickySessionType)}), 108 Optional: true, 109 }, 110 //http & https 111 "cookie_timeout": &schema.Schema{ 112 Type: schema.TypeInt, 113 ValidateFunc: validateSlbListenerCookieTimeout, 114 Optional: true, 115 }, 116 //http & https 117 "cookie": &schema.Schema{ 118 Type: schema.TypeString, 119 ValidateFunc: validateSlbListenerCookie, 120 Optional: true, 121 }, 122 //tcp & udp 123 "persistence_timeout": &schema.Schema{ 124 Type: schema.TypeInt, 125 ValidateFunc: validateSlbListenerPersistenceTimeout, 126 Optional: true, 127 Default: 0, 128 }, 129 //http & https 130 "health_check": &schema.Schema{ 131 Type: schema.TypeString, 132 ValidateFunc: validateAllowedStringValue([]string{ 133 string(slb.OnFlag), 134 string(slb.OffFlag)}), 135 Optional: true, 136 Default: slb.OffFlag, 137 }, 138 //tcp 139 "health_check_type": &schema.Schema{ 140 Type: schema.TypeString, 141 ValidateFunc: validateAllowedStringValue([]string{ 142 string(slb.TCPHealthCheckType), 143 string(slb.HTTPHealthCheckType)}), 144 Optional: true, 145 Default: slb.TCPHealthCheckType, 146 }, 147 //http & https & tcp 148 "health_check_domain": &schema.Schema{ 149 Type: schema.TypeString, 150 ValidateFunc: validateSlbListenerHealthCheckDomain, 151 Optional: true, 152 }, 153 //http & https & tcp 154 "health_check_uri": &schema.Schema{ 155 Type: schema.TypeString, 156 ValidateFunc: validateSlbListenerHealthCheckUri, 157 Optional: true, 158 }, 159 "health_check_connect_port": &schema.Schema{ 160 Type: schema.TypeInt, 161 ValidateFunc: validateSlbListenerHealthCheckConnectPort, 162 Optional: true, 163 }, 164 "healthy_threshold": &schema.Schema{ 165 Type: schema.TypeInt, 166 ValidateFunc: validateIntegerInRange(1, 10), 167 Optional: true, 168 }, 169 "unhealthy_threshold": &schema.Schema{ 170 Type: schema.TypeInt, 171 ValidateFunc: validateIntegerInRange(1, 10), 172 Optional: true, 173 }, 174 175 "health_check_timeout": &schema.Schema{ 176 Type: schema.TypeInt, 177 ValidateFunc: validateIntegerInRange(1, 50), 178 Optional: true, 179 }, 180 "health_check_interval": &schema.Schema{ 181 Type: schema.TypeInt, 182 ValidateFunc: validateIntegerInRange(1, 5), 183 Optional: true, 184 }, 185 //http & https & tcp 186 "health_check_http_code": &schema.Schema{ 187 Type: schema.TypeString, 188 ValidateFunc: validateAllowedSplitStringValue([]string{ 189 string(slb.HTTP_2XX), 190 string(slb.HTTP_3XX), 191 string(slb.HTTP_4XX), 192 string(slb.HTTP_5XX)}, ","), 193 Optional: true, 194 }, 195 //https 196 "ssl_certificate_id": &schema.Schema{ 197 Type: schema.TypeString, 198 Optional: true, 199 }, 200 //https 201 //"ca_certificate_id": &schema.Schema{ 202 // Type: schema.TypeString, 203 // Optional: true, 204 //}, 205 }, 206 }, 207 Set: resourceAliyunSlbListenerHash, 208 }, 209 210 //deprecated 211 "instances": &schema.Schema{ 212 Type: schema.TypeSet, 213 Elem: &schema.Schema{Type: schema.TypeString}, 214 Optional: true, 215 Set: schema.HashString, 216 }, 217 218 "address": &schema.Schema{ 219 Type: schema.TypeString, 220 Computed: true, 221 }, 222 }, 223 } 224 } 225 226 func resourceAliyunSlbCreate(d *schema.ResourceData, meta interface{}) error { 227 228 slbconn := meta.(*AliyunClient).slbconn 229 230 var slbName string 231 if v, ok := d.GetOk("name"); ok { 232 slbName = v.(string) 233 } else { 234 slbName = resource.PrefixedUniqueId("tf-lb-") 235 d.Set("name", slbName) 236 } 237 238 slbArgs := &slb.CreateLoadBalancerArgs{ 239 RegionId: getRegion(d, meta), 240 LoadBalancerName: slbName, 241 } 242 243 if internet, ok := d.GetOk("internet"); ok && internet.(bool) { 244 slbArgs.AddressType = slb.InternetAddressType 245 d.Set("internet", true) 246 } else { 247 slbArgs.AddressType = slb.IntranetAddressType 248 d.Set("internet", false) 249 } 250 251 if v, ok := d.GetOk("internet_charge_type"); ok && v.(string) != "" { 252 slbArgs.InternetChargeType = slb.InternetChargeType(v.(string)) 253 } 254 255 if v, ok := d.GetOk("bandwidth"); ok && v.(int) != 0 { 256 slbArgs.Bandwidth = v.(int) 257 } 258 259 if v, ok := d.GetOk("vswitch_id"); ok && v.(string) != "" { 260 slbArgs.VSwitchId = v.(string) 261 } 262 slb, err := slbconn.CreateLoadBalancer(slbArgs) 263 if err != nil { 264 return err 265 } 266 267 d.SetId(slb.LoadBalancerId) 268 269 return resourceAliyunSlbUpdate(d, meta) 270 } 271 272 func resourceAliyunSlbRead(d *schema.ResourceData, meta interface{}) error { 273 slbconn := meta.(*AliyunClient).slbconn 274 loadBalancer, err := slbconn.DescribeLoadBalancerAttribute(d.Id()) 275 if err != nil { 276 if notFoundError(err) { 277 d.SetId("") 278 return nil 279 } 280 281 return err 282 } 283 284 d.Set("name", loadBalancer.LoadBalancerName) 285 286 if loadBalancer.AddressType == slb.InternetAddressType { 287 d.Set("internal", true) 288 } else { 289 d.Set("internal", false) 290 } 291 d.Set("internet_charge_type", loadBalancer.InternetChargeType) 292 d.Set("bandwidth", loadBalancer.Bandwidth) 293 d.Set("vswitch_id", loadBalancer.VSwitchId) 294 d.Set("address", loadBalancer.Address) 295 296 return nil 297 } 298 299 func resourceAliyunSlbUpdate(d *schema.ResourceData, meta interface{}) error { 300 301 slbconn := meta.(*AliyunClient).slbconn 302 303 d.Partial(true) 304 305 if d.HasChange("name") { 306 err := slbconn.SetLoadBalancerName(d.Id(), d.Get("name").(string)) 307 if err != nil { 308 return err 309 } 310 311 d.SetPartial("name") 312 } 313 314 if d.Get("internet") == true && d.Get("internet_charge_type") == "paybybandwidth" { 315 //don't intranet web and paybybandwidth, then can modify bandwidth 316 if d.HasChange("bandwidth") { 317 args := &slb.ModifyLoadBalancerInternetSpecArgs{ 318 LoadBalancerId: d.Id(), 319 Bandwidth: d.Get("bandwidth").(int), 320 } 321 err := slbconn.ModifyLoadBalancerInternetSpec(args) 322 if err != nil { 323 return err 324 } 325 326 d.SetPartial("bandwidth") 327 } 328 } 329 330 if d.HasChange("listener") { 331 o, n := d.GetChange("listener") 332 os := o.(*schema.Set) 333 ns := n.(*schema.Set) 334 335 remove, _ := expandListeners(os.Difference(ns).List()) 336 add, _ := expandListeners(ns.Difference(os).List()) 337 338 if len(remove) > 0 { 339 for _, listener := range remove { 340 err := slbconn.DeleteLoadBalancerListener(d.Id(), listener.LoadBalancerPort) 341 if err != nil { 342 return fmt.Errorf("Failure removing outdated SLB listeners: %#v", err) 343 } 344 } 345 } 346 347 if len(add) > 0 { 348 for _, listener := range add { 349 err := createListener(slbconn, d.Id(), listener) 350 if err != nil { 351 return fmt.Errorf("Failure add SLB listeners: %#v", err) 352 } 353 } 354 } 355 356 d.SetPartial("listener") 357 } 358 359 // If we currently have instances, or did have instances, 360 // we want to figure out what to add and remove from the load 361 // balancer 362 if d.HasChange("instances") { 363 o, n := d.GetChange("instances") 364 os := o.(*schema.Set) 365 ns := n.(*schema.Set) 366 remove := expandBackendServers(os.Difference(ns).List()) 367 add := expandBackendServers(ns.Difference(os).List()) 368 369 if len(add) > 0 { 370 _, err := slbconn.AddBackendServers(d.Id(), add) 371 if err != nil { 372 return err 373 } 374 } 375 if len(remove) > 0 { 376 removeBackendServers := make([]string, 0, len(remove)) 377 for _, e := range remove { 378 removeBackendServers = append(removeBackendServers, e.ServerId) 379 } 380 _, err := slbconn.RemoveBackendServers(d.Id(), removeBackendServers) 381 if err != nil { 382 return err 383 } 384 } 385 386 d.SetPartial("instances") 387 } 388 389 d.Partial(false) 390 391 return resourceAliyunSlbRead(d, meta) 392 } 393 394 func resourceAliyunSlbDelete(d *schema.ResourceData, meta interface{}) error { 395 conn := meta.(*AliyunClient).slbconn 396 397 return resource.Retry(5*time.Minute, func() *resource.RetryError { 398 err := conn.DeleteLoadBalancer(d.Id()) 399 400 if err != nil { 401 return resource.NonRetryableError(err) 402 } 403 404 loadBalancer, err := conn.DescribeLoadBalancerAttribute(d.Id()) 405 if err != nil { 406 e, _ := err.(*common.Error) 407 if e.ErrorResponse.Code == LoadBalancerNotFound { 408 return nil 409 } 410 return resource.NonRetryableError(err) 411 } 412 if loadBalancer != nil { 413 return resource.RetryableError(fmt.Errorf("LoadBalancer in use - trying again while it deleted.")) 414 } 415 return nil 416 }) 417 } 418 419 func resourceAliyunSlbListenerHash(v interface{}) int { 420 var buf bytes.Buffer 421 m := v.(map[string]interface{}) 422 buf.WriteString(fmt.Sprintf("%d-", m["instance_port"].(int))) 423 buf.WriteString(fmt.Sprintf("%d-", m["lb_port"].(int))) 424 buf.WriteString(fmt.Sprintf("%s-", 425 strings.ToLower(m["lb_protocol"].(string)))) 426 427 buf.WriteString(fmt.Sprintf("%d-", m["bandwidth"].(int))) 428 429 if v, ok := m["ssl_certificate_id"]; ok { 430 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 431 } 432 433 return hashcode.String(buf.String()) 434 } 435 436 func createListener(conn *slb.Client, loadBalancerId string, listener *Listener) error { 437 438 errTypeJudge := func(err error) error { 439 if err != nil { 440 if listenerType, ok := err.(*ListenerErr); ok { 441 if listenerType.ErrType == HealthCheckErrType { 442 return fmt.Errorf("When the HealthCheck is %s, then related HealthCheck parameter "+ 443 "must have.", slb.OnFlag) 444 } else if listenerType.ErrType == StickySessionErrType { 445 return fmt.Errorf("When the StickySession is %s, then StickySessionType parameter "+ 446 "must have.", slb.OnFlag) 447 } else if listenerType.ErrType == CookieTimeOutErrType { 448 return fmt.Errorf("When the StickySession is %s and StickySessionType is %s, "+ 449 "then CookieTimeout parameter must have.", slb.OnFlag, slb.InsertStickySessionType) 450 } else if listenerType.ErrType == CookieErrType { 451 return fmt.Errorf("When the StickySession is %s and StickySessionType is %s, "+ 452 "then Cookie parameter must have.", slb.OnFlag, slb.ServerStickySessionType) 453 } 454 return fmt.Errorf("slb listener check errtype not found.") 455 } 456 } 457 return nil 458 } 459 460 if listener.Protocol == strings.ToLower("tcp") { 461 462 args := getTcpListenerArgs(loadBalancerId, listener) 463 464 if err := conn.CreateLoadBalancerTCPListener(&args); err != nil { 465 return err 466 } 467 } else if listener.Protocol == strings.ToLower("http") { 468 args, argsErr := getHttpListenerArgs(loadBalancerId, listener) 469 if paramErr := errTypeJudge(argsErr); paramErr != nil { 470 return paramErr 471 } 472 473 if err := conn.CreateLoadBalancerHTTPListener(&args); err != nil { 474 return err 475 } 476 } else if listener.Protocol == strings.ToLower("https") { 477 listenerType, err := getHttpListenerType(loadBalancerId, listener) 478 if paramErr := errTypeJudge(err); paramErr != nil { 479 return paramErr 480 } 481 482 args := &slb.CreateLoadBalancerHTTPSListenerArgs{ 483 HTTPListenerType: listenerType, 484 } 485 if listener.SSLCertificateId == "" { 486 return fmt.Errorf("Server Certificated Id cann't be null") 487 } 488 489 args.ServerCertificateId = listener.SSLCertificateId 490 491 if err := conn.CreateLoadBalancerHTTPSListener(args); err != nil { 492 return err 493 } 494 } else if listener.Protocol == strings.ToLower("udp") { 495 args := getUdpListenerArgs(loadBalancerId, listener) 496 497 if err := conn.CreateLoadBalancerUDPListener(&args); err != nil { 498 return err 499 } 500 } 501 502 if err := conn.StartLoadBalancerListener(loadBalancerId, listener.LoadBalancerPort); err != nil { 503 return err 504 } 505 506 return nil 507 } 508 509 func getTcpListenerArgs(loadBalancerId string, listener *Listener) slb.CreateLoadBalancerTCPListenerArgs { 510 args := slb.CreateLoadBalancerTCPListenerArgs{ 511 LoadBalancerId: loadBalancerId, 512 ListenerPort: listener.LoadBalancerPort, 513 BackendServerPort: listener.InstancePort, 514 Bandwidth: listener.Bandwidth, 515 Scheduler: listener.Scheduler, 516 PersistenceTimeout: listener.PersistenceTimeout, 517 HealthCheckType: listener.HealthCheckType, 518 HealthCheckDomain: listener.HealthCheckDomain, 519 HealthCheckURI: listener.HealthCheckURI, 520 HealthCheckConnectPort: listener.HealthCheckConnectPort, 521 HealthyThreshold: listener.HealthyThreshold, 522 UnhealthyThreshold: listener.UnhealthyThreshold, 523 HealthCheckConnectTimeout: listener.HealthCheckTimeout, 524 HealthCheckInterval: listener.HealthCheckInterval, 525 HealthCheckHttpCode: listener.HealthCheckHttpCode, 526 } 527 return args 528 } 529 530 func getUdpListenerArgs(loadBalancerId string, listener *Listener) slb.CreateLoadBalancerUDPListenerArgs { 531 args := slb.CreateLoadBalancerUDPListenerArgs{ 532 LoadBalancerId: loadBalancerId, 533 ListenerPort: listener.LoadBalancerPort, 534 BackendServerPort: listener.InstancePort, 535 Bandwidth: listener.Bandwidth, 536 PersistenceTimeout: listener.PersistenceTimeout, 537 HealthCheckConnectTimeout: listener.HealthCheckTimeout, 538 HealthCheckInterval: listener.HealthCheckInterval, 539 } 540 return args 541 } 542 543 func getHttpListenerType(loadBalancerId string, listener *Listener) (listenType slb.HTTPListenerType, err error) { 544 545 if listener.HealthCheck == slb.OnFlag { 546 if listener.HealthCheckURI == "" || listener.HealthCheckDomain == "" || listener.HealthCheckConnectPort == 0 || 547 listener.HealthyThreshold == 0 || listener.UnhealthyThreshold == 0 || listener.HealthCheckTimeout == 0 || 548 listener.HealthCheckHttpCode == "" || listener.HealthCheckInterval == 0 { 549 550 errMsg := errors.New("err: HealthCheck empty.") 551 return listenType, &ListenerErr{HealthCheckErrType, errMsg} 552 } 553 } 554 555 if listener.StickySession == slb.OnFlag { 556 if listener.StickySessionType == "" { 557 errMsg := errors.New("err: stickySession empty.") 558 return listenType, &ListenerErr{StickySessionErrType, errMsg} 559 } 560 561 if listener.StickySessionType == slb.InsertStickySessionType { 562 if listener.CookieTimeout == 0 { 563 errMsg := errors.New("err: cookieTimeout empty.") 564 return listenType, &ListenerErr{CookieTimeOutErrType, errMsg} 565 } 566 } else if listener.StickySessionType == slb.ServerStickySessionType { 567 if listener.Cookie == "" { 568 errMsg := errors.New("err: cookie empty.") 569 return listenType, &ListenerErr{CookieErrType, errMsg} 570 } 571 } 572 } 573 574 httpListenertType := slb.HTTPListenerType{ 575 LoadBalancerId: loadBalancerId, 576 ListenerPort: listener.LoadBalancerPort, 577 BackendServerPort: listener.InstancePort, 578 Bandwidth: listener.Bandwidth, 579 Scheduler: listener.Scheduler, 580 HealthCheck: listener.HealthCheck, 581 StickySession: listener.StickySession, 582 StickySessionType: listener.StickySessionType, 583 CookieTimeout: listener.CookieTimeout, 584 Cookie: listener.Cookie, 585 HealthCheckDomain: listener.HealthCheckDomain, 586 HealthCheckURI: listener.HealthCheckURI, 587 HealthCheckConnectPort: listener.HealthCheckConnectPort, 588 HealthyThreshold: listener.HealthyThreshold, 589 UnhealthyThreshold: listener.UnhealthyThreshold, 590 HealthCheckTimeout: listener.HealthCheckTimeout, 591 HealthCheckInterval: listener.HealthCheckInterval, 592 HealthCheckHttpCode: listener.HealthCheckHttpCode, 593 } 594 595 return httpListenertType, err 596 } 597 598 func getHttpListenerArgs(loadBalancerId string, listener *Listener) (listenType slb.CreateLoadBalancerHTTPListenerArgs, err error) { 599 httpListenerType, err := getHttpListenerType(loadBalancerId, listener) 600 if err != nil { 601 return listenType, err 602 } 603 604 httpArgs := slb.CreateLoadBalancerHTTPListenerArgs(httpListenerType) 605 return httpArgs, err 606 }