-
Notifications
You must be signed in to change notification settings - Fork 9.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OpenStack LBaaS V2 support - Pool Resource
CRUD, tests, and docs for managing a pool resource
- Loading branch information
Showing
3 changed files
with
563 additions
and
0 deletions.
There are no files selected for viewing
322 changes: 322 additions & 0 deletions
322
builtin/providers/openstack/resource_openstack_lbaas_pool_v2.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,322 @@ | ||
package openstack | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"time" | ||
|
||
"github.com/hashicorp/terraform/helper/resource" | ||
"github.com/hashicorp/terraform/helper/schema" | ||
|
||
"github.com/rackspace/gophercloud" | ||
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" | ||
) | ||
|
||
func resourcePoolV2() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourcePoolV2Create, | ||
Read: resourcePoolV2Read, | ||
Delete: resourcePoolV2Delete, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"region": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), | ||
}, | ||
|
||
"tenant_id": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Computed: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"name": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"description": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"protocol": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { | ||
value := v.(string) | ||
if value != "lTCP" && value != "HTTP" && value != "HTTPS" { | ||
errors = append(errors, fmt.Errorf( | ||
"Only 'TCP', 'HTTP', and 'HTTPS' are supported values for 'protocol'")) | ||
} | ||
return | ||
}, | ||
}, | ||
|
||
// One of loadbalancer_id or listener_id must be provided | ||
"loadbalancer_id": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
}, | ||
|
||
// One of loadbalancer_id or listener_id must be provided | ||
"listener_id": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"lb_method": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { | ||
value := v.(string) | ||
if value != "ROUND_ROBIN" && value != "LEAST_CONNECTIONS" && value != "SOURCE_IP" { | ||
errors = append(errors, fmt.Errorf( | ||
"Only 'ROUND_ROBIN', 'LEAST_CONNECTIONS', and 'SOURCE_IP' are supported values for 'lb_method'")) | ||
} | ||
return | ||
}, | ||
}, | ||
|
||
"persistence": &schema.Schema{ | ||
Type: schema.TypeList, | ||
Optional: true, | ||
ForceNew: true, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"type": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { | ||
value := v.(string) | ||
if value != "SOURCE_IP" && value != "HTTP_COOKIE" && value != "APP_COOKIE" { | ||
errors = append(errors, fmt.Errorf( | ||
"Only 'SOURCE_IP', 'HTTP_COOKIE', and 'APP_COOKIE' are supported values for 'persistence'")) | ||
} | ||
return | ||
}, | ||
}, | ||
|
||
"cookie_name": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
|
||
"admin_state_up": &schema.Schema{ | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"id": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Computed: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourcePoolV2Create(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
networkingClient, err := config.networkingV2Client(d.Get("region").(string)) | ||
if err != nil { | ||
return fmt.Errorf("Error creating OpenStack networking client: %s", err) | ||
} | ||
|
||
adminStateUp := d.Get("admin_state_up").(bool) | ||
var persistence pools.SessionPersistence | ||
if p, ok := d.GetOk("persistence"); ok { | ||
pV := (p.([]interface{}))[0].(map[string]interface{}) | ||
|
||
persistence = pools.SessionPersistence{ | ||
Type: pV["type"].(string), | ||
CookieName: pV["cookie_name"].(string), | ||
} | ||
} | ||
createOpts := pools.CreateOpts{ | ||
TenantID: d.Get("tenant_id").(string), | ||
Name: d.Get("name").(string), | ||
Description: d.Get("description").(string), | ||
Protocol: pools.Protocol(d.Get("protocol").(string)), | ||
LoadbalancerID: d.Get("loadbalancer_id").(string), | ||
ListenerID: d.Get("listener_id").(string), | ||
LBMethod: pools.LBMethod(d.Get("lb_method").(string)), | ||
AdminStateUp: &adminStateUp, | ||
} | ||
// Must omit if not set | ||
if persistence != (pools.SessionPersistence{}) { | ||
createOpts.Persistence = &persistence | ||
} | ||
|
||
log.Printf("[DEBUG] Create Options: %#v", createOpts) | ||
pool, err := pools.Create(networkingClient, createOpts).Extract() | ||
if err != nil { | ||
return fmt.Errorf("Error creating OpenStack LBaaSV2 pool: %s", err) | ||
} | ||
log.Printf("[INFO] pool ID: %s", pool.ID) | ||
|
||
log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 pool (%s) to become available.", pool.ID) | ||
|
||
stateConf := &resource.StateChangeConf{ | ||
Pending: []string{"PENDING_CREATE"}, | ||
Target: []string{"ACTIVE"}, | ||
Refresh: waitForPoolActive(networkingClient, pool.ID), | ||
Timeout: 2 * time.Minute, | ||
Delay: 5 * time.Second, | ||
MinTimeout: 3 * time.Second, | ||
} | ||
|
||
_, err = stateConf.WaitForState() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
d.SetId(pool.ID) | ||
|
||
return resourcePoolV2Read(d, meta) | ||
} | ||
|
||
func resourcePoolV2Read(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
networkingClient, err := config.networkingV2Client(d.Get("region").(string)) | ||
if err != nil { | ||
return fmt.Errorf("Error creating OpenStack networking client: %s", err) | ||
} | ||
|
||
pool, err := pools.Get(networkingClient, d.Id()).Extract() | ||
if err != nil { | ||
return CheckDeleted(d, err, "LBV2 Pool") | ||
} | ||
|
||
log.Printf("[DEBUG] Retreived OpenStack LBaaSV2 Pool %s: %+v", d.Id(), pool) | ||
|
||
d.Set("lb_method", pool.LBMethod) | ||
d.Set("protocol", pool.Protocol) | ||
d.Set("description", pool.Description) | ||
d.Set("tenant_id", pool.TenantID) | ||
d.Set("admin_state_up", pool.AdminStateUp) | ||
d.Set("name", pool.Name) | ||
d.Set("id", pool.ID) | ||
d.Set("persistence", pool.Persistence) | ||
|
||
return nil | ||
} | ||
|
||
func resourcePoolV2Update(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
networkingClient, err := config.networkingV2Client(d.Get("region").(string)) | ||
if err != nil { | ||
return fmt.Errorf("Error creating OpenStack networking client: %s", err) | ||
} | ||
|
||
var updateOpts pools.UpdateOpts | ||
if d.HasChange("lb_method") { | ||
updateOpts.LBMethod = pools.LBMethod(d.Get("lb_method").(string)) | ||
} | ||
if d.HasChange("name") { | ||
updateOpts.Name = d.Get("name").(string) | ||
} | ||
if d.HasChange("description") { | ||
updateOpts.Description = d.Get("description").(string) | ||
} | ||
if d.HasChange("admin_state_up") { | ||
asu := d.Get("admin_state_up").(bool) | ||
updateOpts.AdminStateUp = &asu | ||
} | ||
|
||
log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Pool %s with options: %+v", d.Id(), updateOpts) | ||
|
||
_, err = pools.Update(networkingClient, d.Id(), updateOpts).Extract() | ||
if err != nil { | ||
return fmt.Errorf("Error updating OpenStack LBaaSV2 Pool: %s", err) | ||
} | ||
|
||
return resourcePoolV2Read(d, meta) | ||
} | ||
|
||
func resourcePoolV2Delete(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
networkingClient, err := config.networkingV2Client(d.Get("region").(string)) | ||
if err != nil { | ||
return fmt.Errorf("Error creating OpenStack networking client: %s", err) | ||
} | ||
|
||
stateConf := &resource.StateChangeConf{ | ||
Pending: []string{"ACTIVE", "PENDING_DELETE"}, | ||
Target: []string{"DELETED"}, | ||
Refresh: waitForPoolDelete(networkingClient, d.Id()), | ||
Timeout: 2 * time.Minute, | ||
Delay: 5 * time.Second, | ||
MinTimeout: 3 * time.Second, | ||
} | ||
|
||
_, err = stateConf.WaitForState() | ||
if err != nil { | ||
return fmt.Errorf("Error deleting OpenStack LBaaSV2 Pool: %s", err) | ||
} | ||
|
||
d.SetId("") | ||
return nil | ||
} | ||
|
||
func waitForPoolActive(networkingClient *gophercloud.ServiceClient, poolID string) resource.StateRefreshFunc { | ||
return func() (interface{}, string, error) { | ||
pool, err := pools.Get(networkingClient, poolID).Extract() | ||
if err != nil { | ||
return nil, "", err | ||
} | ||
|
||
// The pool resource has no Status attribute, so a successful Get is the best we can do | ||
log.Printf("[DEBUG] OpenStack LBaaSV2 Pool: %+v", pool) | ||
return pool, "ACTIVE", nil | ||
} | ||
} | ||
|
||
func waitForPoolDelete(networkingClient *gophercloud.ServiceClient, poolID string) resource.StateRefreshFunc { | ||
return func() (interface{}, string, error) { | ||
log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Pool %s", poolID) | ||
|
||
pool, err := pools.Get(networkingClient, poolID).Extract() | ||
if err != nil { | ||
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) | ||
if !ok { | ||
return pool, "ACTIVE", err | ||
} | ||
if errCode.Actual == 404 { | ||
log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID) | ||
return pool, "DELETED", nil | ||
} | ||
} | ||
|
||
log.Printf("[DEBUG] Openstack LBaaSV2 Pool: %+v", pool) | ||
err = pools.Delete(networkingClient, poolID).ExtractErr() | ||
if err != nil { | ||
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) | ||
if !ok { | ||
return pool, "ACTIVE", err | ||
} | ||
if errCode.Actual == 404 { | ||
log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID) | ||
return pool, "DELETED", nil | ||
} | ||
} | ||
|
||
log.Printf("[DEBUG] OpenStack LBaaSV2 Pool %s still active.", poolID) | ||
return pool, "ACTIVE", nil | ||
} | ||
} |
Oops, something went wrong.