github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/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 if loadBalancer == nil { 285 d.SetId("") 286 return nil 287 } 288 289 d.Set("name", loadBalancer.LoadBalancerName) 290 291 if loadBalancer.AddressType == slb.InternetAddressType { 292 d.Set("internal", true) 293 } else { 294 d.Set("internal", false) 295 } 296 d.Set("internet_charge_type", loadBalancer.InternetChargeType) 297 d.Set("bandwidth", loadBalancer.Bandwidth) 298 d.Set("vswitch_id", loadBalancer.VSwitchId) 299 d.Set("address", loadBalancer.Address) 300 301 return nil 302 } 303 304 func resourceAliyunSlbUpdate(d *schema.ResourceData, meta interface{}) error { 305 306 slbconn := meta.(*AliyunClient).slbconn 307 308 d.Partial(true) 309 310 if d.HasChange("name") { 311 err := slbconn.SetLoadBalancerName(d.Id(), d.Get("name").(string)) 312 if err != nil { 313 return err 314 } 315 316 d.SetPartial("name") 317 } 318 319 if d.Get("internet") == true && d.Get("internet_charge_type") == "paybybandwidth" { 320 //don't intranet web and paybybandwidth, then can modify bandwidth 321 if d.HasChange("bandwidth") { 322 args := &slb.ModifyLoadBalancerInternetSpecArgs{ 323 LoadBalancerId: d.Id(), 324 Bandwidth: d.Get("bandwidth").(int), 325 } 326 err := slbconn.ModifyLoadBalancerInternetSpec(args) 327 if err != nil { 328 return err 329 } 330 331 d.SetPartial("bandwidth") 332 } 333 } 334 335 if d.HasChange("listener") { 336 o, n := d.GetChange("listener") 337 os := o.(*schema.Set) 338 ns := n.(*schema.Set) 339 340 remove, _ := expandListeners(os.Difference(ns).List()) 341 add, _ := expandListeners(ns.Difference(os).List()) 342 343 if len(remove) > 0 { 344 for _, listener := range remove { 345 err := slbconn.DeleteLoadBalancerListener(d.Id(), listener.LoadBalancerPort) 346 if err != nil { 347 return fmt.Errorf("Failure removing outdated SLB listeners: %#v", err) 348 } 349 } 350 } 351 352 if len(add) > 0 { 353 for _, listener := range add { 354 err := createListener(slbconn, d.Id(), listener) 355 if err != nil { 356 return fmt.Errorf("Failure add SLB listeners: %#v", err) 357 } 358 } 359 } 360 361 d.SetPartial("listener") 362 } 363 364 // If we currently have instances, or did have instances, 365 // we want to figure out what to add and remove from the load 366 // balancer 367 if d.HasChange("instances") { 368 o, n := d.GetChange("instances") 369 os := o.(*schema.Set) 370 ns := n.(*schema.Set) 371 remove := expandBackendServers(os.Difference(ns).List()) 372 add := expandBackendServers(ns.Difference(os).List()) 373 374 if len(add) > 0 { 375 _, err := slbconn.AddBackendServers(d.Id(), add) 376 if err != nil { 377 return err 378 } 379 } 380 if len(remove) > 0 { 381 removeBackendServers := make([]string, 0, len(remove)) 382 for _, e := range remove { 383 removeBackendServers = append(removeBackendServers, e.ServerId) 384 } 385 _, err := slbconn.RemoveBackendServers(d.Id(), removeBackendServers) 386 if err != nil { 387 return err 388 } 389 } 390 391 d.SetPartial("instances") 392 } 393 394 d.Partial(false) 395 396 return resourceAliyunSlbRead(d, meta) 397 } 398 399 func resourceAliyunSlbDelete(d *schema.ResourceData, meta interface{}) error { 400 conn := meta.(*AliyunClient).slbconn 401 402 return resource.Retry(5*time.Minute, func() *resource.RetryError { 403 err := conn.DeleteLoadBalancer(d.Id()) 404 405 if err != nil { 406 return resource.NonRetryableError(err) 407 } 408 409 loadBalancer, err := conn.DescribeLoadBalancerAttribute(d.Id()) 410 if err != nil { 411 e, _ := err.(*common.Error) 412 if e.ErrorResponse.Code == LoadBalancerNotFound { 413 return nil 414 } 415 return resource.NonRetryableError(err) 416 } 417 if loadBalancer != nil { 418 return resource.RetryableError(fmt.Errorf("LoadBalancer in use - trying again while it deleted.")) 419 } 420 return nil 421 }) 422 } 423 424 func resourceAliyunSlbListenerHash(v interface{}) int { 425 var buf bytes.Buffer 426 m := v.(map[string]interface{}) 427 buf.WriteString(fmt.Sprintf("%d-", m["instance_port"].(int))) 428 buf.WriteString(fmt.Sprintf("%d-", m["lb_port"].(int))) 429 buf.WriteString(fmt.Sprintf("%s-", 430 strings.ToLower(m["lb_protocol"].(string)))) 431 432 buf.WriteString(fmt.Sprintf("%d-", m["bandwidth"].(int))) 433 434 if v, ok := m["ssl_certificate_id"]; ok { 435 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 436 } 437 438 return hashcode.String(buf.String()) 439 } 440 441 func createListener(conn *slb.Client, loadBalancerId string, listener *Listener) error { 442 443 errTypeJudge := func(err error) error { 444 if err != nil { 445 if listenerType, ok := err.(*ListenerErr); ok { 446 if listenerType.ErrType == HealthCheckErrType { 447 return fmt.Errorf("When the HealthCheck is %s, then related HealthCheck parameter "+ 448 "must have.", slb.OnFlag) 449 } else if listenerType.ErrType == StickySessionErrType { 450 return fmt.Errorf("When the StickySession is %s, then StickySessionType parameter "+ 451 "must have.", slb.OnFlag) 452 } else if listenerType.ErrType == CookieTimeOutErrType { 453 return fmt.Errorf("When the StickySession is %s and StickySessionType is %s, "+ 454 "then CookieTimeout parameter must have.", slb.OnFlag, slb.InsertStickySessionType) 455 } else if listenerType.ErrType == CookieErrType { 456 return fmt.Errorf("When the StickySession is %s and StickySessionType is %s, "+ 457 "then Cookie parameter must have.", slb.OnFlag, slb.ServerStickySessionType) 458 } 459 return fmt.Errorf("slb listener check errtype not found.") 460 } 461 } 462 return nil 463 } 464 465 if listener.Protocol == strings.ToLower("tcp") { 466 467 args := getTcpListenerArgs(loadBalancerId, listener) 468 469 if err := conn.CreateLoadBalancerTCPListener(&args); err != nil { 470 return err 471 } 472 } else if listener.Protocol == strings.ToLower("http") { 473 args, argsErr := getHttpListenerArgs(loadBalancerId, listener) 474 if paramErr := errTypeJudge(argsErr); paramErr != nil { 475 return paramErr 476 } 477 478 if err := conn.CreateLoadBalancerHTTPListener(&args); err != nil { 479 return err 480 } 481 } else if listener.Protocol == strings.ToLower("https") { 482 listenerType, err := getHttpListenerType(loadBalancerId, listener) 483 if paramErr := errTypeJudge(err); paramErr != nil { 484 return paramErr 485 } 486 487 args := &slb.CreateLoadBalancerHTTPSListenerArgs{ 488 HTTPListenerType: listenerType, 489 } 490 if listener.SSLCertificateId == "" { 491 return fmt.Errorf("Server Certificated Id cann't be null") 492 } 493 494 args.ServerCertificateId = listener.SSLCertificateId 495 496 if err := conn.CreateLoadBalancerHTTPSListener(args); err != nil { 497 return err 498 } 499 } else if listener.Protocol == strings.ToLower("udp") { 500 args := getUdpListenerArgs(loadBalancerId, listener) 501 502 if err := conn.CreateLoadBalancerUDPListener(&args); err != nil { 503 return err 504 } 505 } 506 507 if err := conn.StartLoadBalancerListener(loadBalancerId, listener.LoadBalancerPort); err != nil { 508 return err 509 } 510 511 return nil 512 } 513 514 func getTcpListenerArgs(loadBalancerId string, listener *Listener) slb.CreateLoadBalancerTCPListenerArgs { 515 args := slb.CreateLoadBalancerTCPListenerArgs{ 516 LoadBalancerId: loadBalancerId, 517 ListenerPort: listener.LoadBalancerPort, 518 BackendServerPort: listener.InstancePort, 519 Bandwidth: listener.Bandwidth, 520 Scheduler: listener.Scheduler, 521 PersistenceTimeout: listener.PersistenceTimeout, 522 HealthCheckType: listener.HealthCheckType, 523 HealthCheckDomain: listener.HealthCheckDomain, 524 HealthCheckURI: listener.HealthCheckURI, 525 HealthCheckConnectPort: listener.HealthCheckConnectPort, 526 HealthyThreshold: listener.HealthyThreshold, 527 UnhealthyThreshold: listener.UnhealthyThreshold, 528 HealthCheckConnectTimeout: listener.HealthCheckTimeout, 529 HealthCheckInterval: listener.HealthCheckInterval, 530 HealthCheckHttpCode: listener.HealthCheckHttpCode, 531 } 532 return args 533 } 534 535 func getUdpListenerArgs(loadBalancerId string, listener *Listener) slb.CreateLoadBalancerUDPListenerArgs { 536 args := slb.CreateLoadBalancerUDPListenerArgs{ 537 LoadBalancerId: loadBalancerId, 538 ListenerPort: listener.LoadBalancerPort, 539 BackendServerPort: listener.InstancePort, 540 Bandwidth: listener.Bandwidth, 541 PersistenceTimeout: listener.PersistenceTimeout, 542 HealthCheckConnectTimeout: listener.HealthCheckTimeout, 543 HealthCheckInterval: listener.HealthCheckInterval, 544 } 545 return args 546 } 547 548 func getHttpListenerType(loadBalancerId string, listener *Listener) (listenType slb.HTTPListenerType, err error) { 549 550 if listener.HealthCheck == slb.OnFlag { 551 if listener.HealthCheckURI == "" || listener.HealthCheckDomain == "" || listener.HealthCheckConnectPort == 0 || 552 listener.HealthyThreshold == 0 || listener.UnhealthyThreshold == 0 || listener.HealthCheckTimeout == 0 || 553 listener.HealthCheckHttpCode == "" || listener.HealthCheckInterval == 0 { 554 555 errMsg := errors.New("err: HealthCheck empty.") 556 return listenType, &ListenerErr{HealthCheckErrType, errMsg} 557 } 558 } 559 560 if listener.StickySession == slb.OnFlag { 561 if listener.StickySessionType == "" { 562 errMsg := errors.New("err: stickySession empty.") 563 return listenType, &ListenerErr{StickySessionErrType, errMsg} 564 } 565 566 if listener.StickySessionType == slb.InsertStickySessionType { 567 if listener.CookieTimeout == 0 { 568 errMsg := errors.New("err: cookieTimeout empty.") 569 return listenType, &ListenerErr{CookieTimeOutErrType, errMsg} 570 } 571 } else if listener.StickySessionType == slb.ServerStickySessionType { 572 if listener.Cookie == "" { 573 errMsg := errors.New("err: cookie empty.") 574 return listenType, &ListenerErr{CookieErrType, errMsg} 575 } 576 } 577 } 578 579 httpListenertType := slb.HTTPListenerType{ 580 LoadBalancerId: loadBalancerId, 581 ListenerPort: listener.LoadBalancerPort, 582 BackendServerPort: listener.InstancePort, 583 Bandwidth: listener.Bandwidth, 584 Scheduler: listener.Scheduler, 585 HealthCheck: listener.HealthCheck, 586 StickySession: listener.StickySession, 587 StickySessionType: listener.StickySessionType, 588 CookieTimeout: listener.CookieTimeout, 589 Cookie: listener.Cookie, 590 HealthCheckDomain: listener.HealthCheckDomain, 591 HealthCheckURI: listener.HealthCheckURI, 592 HealthCheckConnectPort: listener.HealthCheckConnectPort, 593 HealthyThreshold: listener.HealthyThreshold, 594 UnhealthyThreshold: listener.UnhealthyThreshold, 595 HealthCheckTimeout: listener.HealthCheckTimeout, 596 HealthCheckInterval: listener.HealthCheckInterval, 597 HealthCheckHttpCode: listener.HealthCheckHttpCode, 598 } 599 600 return httpListenertType, err 601 } 602 603 func getHttpListenerArgs(loadBalancerId string, listener *Listener) (listenType slb.CreateLoadBalancerHTTPListenerArgs, err error) { 604 httpListenerType, err := getHttpListenerType(loadBalancerId, listener) 605 if err != nil { 606 return listenType, err 607 } 608 609 httpArgs := slb.CreateLoadBalancerHTTPListenerArgs(httpListenerType) 610 return httpArgs, err 611 }