github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/openstack/resource_openstack_lb_pool_v1.go (about)

     1  package openstack
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/helper/hashcode"
    10  	"github.com/hashicorp/terraform/helper/resource"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  
    13  	"github.com/gophercloud/gophercloud"
    14  	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members"
    15  	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools"
    16  	"github.com/gophercloud/gophercloud/pagination"
    17  )
    18  
    19  func resourceLBPoolV1() *schema.Resource {
    20  	return &schema.Resource{
    21  		Create: resourceLBPoolV1Create,
    22  		Read:   resourceLBPoolV1Read,
    23  		Update: resourceLBPoolV1Update,
    24  		Delete: resourceLBPoolV1Delete,
    25  		Importer: &schema.ResourceImporter{
    26  			State: schema.ImportStatePassthrough,
    27  		},
    28  
    29  		Timeouts: &schema.ResourceTimeout{
    30  			Create: schema.DefaultTimeout(10 * time.Minute),
    31  			Delete: schema.DefaultTimeout(10 * time.Minute),
    32  		},
    33  
    34  		Schema: map[string]*schema.Schema{
    35  			"region": &schema.Schema{
    36  				Type:        schema.TypeString,
    37  				Required:    true,
    38  				ForceNew:    true,
    39  				DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
    40  			},
    41  			"name": &schema.Schema{
    42  				Type:     schema.TypeString,
    43  				Required: true,
    44  				ForceNew: false,
    45  			},
    46  			"protocol": &schema.Schema{
    47  				Type:     schema.TypeString,
    48  				Required: true,
    49  				ForceNew: true,
    50  			},
    51  			"subnet_id": &schema.Schema{
    52  				Type:     schema.TypeString,
    53  				Required: true,
    54  				ForceNew: true,
    55  			},
    56  
    57  			"lb_method": &schema.Schema{
    58  				Type:     schema.TypeString,
    59  				Required: true,
    60  				ForceNew: false,
    61  			},
    62  			"lb_provider": &schema.Schema{
    63  				Type:     schema.TypeString,
    64  				Optional: true,
    65  				Computed: true,
    66  				ForceNew: true,
    67  			},
    68  			"tenant_id": &schema.Schema{
    69  				Type:     schema.TypeString,
    70  				Optional: true,
    71  				ForceNew: true,
    72  				Computed: true,
    73  			},
    74  			"member": &schema.Schema{
    75  				Type:       schema.TypeSet,
    76  				Deprecated: "Use openstack_lb_member_v1 instead. This attribute will be removed in a future version.",
    77  				Optional:   true,
    78  				Elem: &schema.Resource{
    79  					Schema: map[string]*schema.Schema{
    80  						"region": &schema.Schema{
    81  							Type:        schema.TypeString,
    82  							Required:    true,
    83  							ForceNew:    true,
    84  							DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
    85  						},
    86  						"tenant_id": &schema.Schema{
    87  							Type:     schema.TypeString,
    88  							Optional: true,
    89  							ForceNew: true,
    90  						},
    91  						"address": &schema.Schema{
    92  							Type:     schema.TypeString,
    93  							Required: true,
    94  							ForceNew: true,
    95  						},
    96  						"port": &schema.Schema{
    97  							Type:     schema.TypeInt,
    98  							Required: true,
    99  							ForceNew: true,
   100  						},
   101  						"admin_state_up": &schema.Schema{
   102  							Type:     schema.TypeBool,
   103  							Required: true,
   104  							ForceNew: false,
   105  						},
   106  					},
   107  				},
   108  				Set: resourceLBMemberV1Hash,
   109  			},
   110  			"monitor_ids": &schema.Schema{
   111  				Type:     schema.TypeSet,
   112  				Optional: true,
   113  				ForceNew: false,
   114  				Elem:     &schema.Schema{Type: schema.TypeString},
   115  				Set:      schema.HashString,
   116  			},
   117  		},
   118  	}
   119  }
   120  
   121  func resourceLBPoolV1Create(d *schema.ResourceData, meta interface{}) error {
   122  	config := meta.(*Config)
   123  	networkingClient, err := config.networkingV2Client(GetRegion(d))
   124  	if err != nil {
   125  		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
   126  	}
   127  
   128  	createOpts := pools.CreateOpts{
   129  		Name:     d.Get("name").(string),
   130  		SubnetID: d.Get("subnet_id").(string),
   131  		TenantID: d.Get("tenant_id").(string),
   132  		Provider: d.Get("lb_provider").(string),
   133  	}
   134  
   135  	if v, ok := d.GetOk("protocol"); ok {
   136  		protocol := resourceLBPoolV1DetermineProtocol(v.(string))
   137  		createOpts.Protocol = protocol
   138  	}
   139  
   140  	if v, ok := d.GetOk("lb_method"); ok {
   141  		lbMethod := resourceLBPoolV1DetermineLBMethod(v.(string))
   142  		createOpts.LBMethod = lbMethod
   143  	}
   144  
   145  	log.Printf("[DEBUG] Create Options: %#v", createOpts)
   146  	p, err := pools.Create(networkingClient, createOpts).Extract()
   147  	if err != nil {
   148  		return fmt.Errorf("Error creating OpenStack LB pool: %s", err)
   149  	}
   150  	log.Printf("[INFO] LB Pool ID: %s", p.ID)
   151  
   152  	log.Printf("[DEBUG] Waiting for OpenStack LB pool (%s) to become available.", p.ID)
   153  
   154  	stateConf := &resource.StateChangeConf{
   155  		Pending:    []string{"PENDING_CREATE"},
   156  		Target:     []string{"ACTIVE"},
   157  		Refresh:    waitForLBPoolActive(networkingClient, p.ID),
   158  		Timeout:    d.Timeout(schema.TimeoutCreate),
   159  		Delay:      5 * time.Second,
   160  		MinTimeout: 3 * time.Second,
   161  	}
   162  
   163  	_, err = stateConf.WaitForState()
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	d.SetId(p.ID)
   169  
   170  	if mIDs := resourcePoolMonitorIDsV1(d); mIDs != nil {
   171  		for _, mID := range mIDs {
   172  			_, err := pools.AssociateMonitor(networkingClient, p.ID, mID).Extract()
   173  			if err != nil {
   174  				return fmt.Errorf("Error associating monitor (%s) with OpenStack LB pool (%s): %s", mID, p.ID, err)
   175  			}
   176  		}
   177  	}
   178  
   179  	if memberOpts := resourcePoolMembersV1(d); memberOpts != nil {
   180  		for _, memberOpt := range memberOpts {
   181  			_, err := members.Create(networkingClient, memberOpt).Extract()
   182  			if err != nil {
   183  				return fmt.Errorf("Error creating OpenStack LB member: %s", err)
   184  			}
   185  		}
   186  	}
   187  
   188  	return resourceLBPoolV1Read(d, meta)
   189  }
   190  
   191  func resourceLBPoolV1Read(d *schema.ResourceData, meta interface{}) error {
   192  	config := meta.(*Config)
   193  	networkingClient, err := config.networkingV2Client(GetRegion(d))
   194  	if err != nil {
   195  		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
   196  	}
   197  
   198  	p, err := pools.Get(networkingClient, d.Id()).Extract()
   199  	if err != nil {
   200  		return CheckDeleted(d, err, "LB pool")
   201  	}
   202  
   203  	log.Printf("[DEBUG] Retrieved OpenStack LB Pool %s: %+v", d.Id(), p)
   204  
   205  	d.Set("name", p.Name)
   206  	d.Set("protocol", p.Protocol)
   207  	d.Set("subnet_id", p.SubnetID)
   208  	d.Set("lb_method", p.LBMethod)
   209  	d.Set("lb_provider", p.Provider)
   210  	d.Set("tenant_id", p.TenantID)
   211  	d.Set("monitor_ids", p.MonitorIDs)
   212  	d.Set("member_ids", p.MemberIDs)
   213  	d.Set("region", GetRegion(d))
   214  
   215  	return nil
   216  }
   217  
   218  func resourceLBPoolV1Update(d *schema.ResourceData, meta interface{}) error {
   219  	config := meta.(*Config)
   220  	networkingClient, err := config.networkingV2Client(GetRegion(d))
   221  	if err != nil {
   222  		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
   223  	}
   224  
   225  	var updateOpts pools.UpdateOpts
   226  	// If either option changed, update both.
   227  	// Gophercloud complains if one is empty.
   228  	if d.HasChange("name") || d.HasChange("lb_method") {
   229  		updateOpts.Name = d.Get("name").(string)
   230  
   231  		lbMethod := resourceLBPoolV1DetermineLBMethod(d.Get("lb_method").(string))
   232  		updateOpts.LBMethod = lbMethod
   233  	}
   234  
   235  	log.Printf("[DEBUG] Updating OpenStack LB Pool %s with options: %+v", d.Id(), updateOpts)
   236  
   237  	_, err = pools.Update(networkingClient, d.Id(), updateOpts).Extract()
   238  	if err != nil {
   239  		return fmt.Errorf("Error updating OpenStack LB Pool: %s", err)
   240  	}
   241  
   242  	if d.HasChange("monitor_ids") {
   243  		oldMIDsRaw, newMIDsRaw := d.GetChange("monitor_ids")
   244  		oldMIDsSet, newMIDsSet := oldMIDsRaw.(*schema.Set), newMIDsRaw.(*schema.Set)
   245  		monitorsToAdd := newMIDsSet.Difference(oldMIDsSet)
   246  		monitorsToRemove := oldMIDsSet.Difference(newMIDsSet)
   247  
   248  		log.Printf("[DEBUG] Monitors to add: %v", monitorsToAdd)
   249  
   250  		log.Printf("[DEBUG] Monitors to remove: %v", monitorsToRemove)
   251  
   252  		for _, m := range monitorsToAdd.List() {
   253  			_, err := pools.AssociateMonitor(networkingClient, d.Id(), m.(string)).Extract()
   254  			if err != nil {
   255  				return fmt.Errorf("Error associating monitor (%s) with OpenStack server (%s): %s", m.(string), d.Id(), err)
   256  			}
   257  			log.Printf("[DEBUG] Associated monitor (%s) with pool (%s)", m.(string), d.Id())
   258  		}
   259  
   260  		for _, m := range monitorsToRemove.List() {
   261  			_, err := pools.DisassociateMonitor(networkingClient, d.Id(), m.(string)).Extract()
   262  			if err != nil {
   263  				return fmt.Errorf("Error disassociating monitor (%s) from OpenStack server (%s): %s", m.(string), d.Id(), err)
   264  			}
   265  			log.Printf("[DEBUG] Disassociated monitor (%s) from pool (%s)", m.(string), d.Id())
   266  		}
   267  	}
   268  
   269  	if d.HasChange("member") {
   270  		oldMembersRaw, newMembersRaw := d.GetChange("member")
   271  		oldMembersSet, newMembersSet := oldMembersRaw.(*schema.Set), newMembersRaw.(*schema.Set)
   272  		membersToAdd := newMembersSet.Difference(oldMembersSet)
   273  		membersToRemove := oldMembersSet.Difference(newMembersSet)
   274  
   275  		log.Printf("[DEBUG] Members to add: %v", membersToAdd)
   276  
   277  		log.Printf("[DEBUG] Members to remove: %v", membersToRemove)
   278  
   279  		for _, m := range membersToRemove.List() {
   280  			oldMember := resourcePoolMemberV1(d, m)
   281  			listOpts := members.ListOpts{
   282  				PoolID:       d.Id(),
   283  				Address:      oldMember.Address,
   284  				ProtocolPort: oldMember.ProtocolPort,
   285  			}
   286  			err = members.List(networkingClient, listOpts).EachPage(func(page pagination.Page) (bool, error) {
   287  				extractedMembers, err := members.ExtractMembers(page)
   288  				if err != nil {
   289  					return false, err
   290  				}
   291  				for _, member := range extractedMembers {
   292  					err := members.Delete(networkingClient, member.ID).ExtractErr()
   293  					if err != nil {
   294  						return false, fmt.Errorf("Error deleting member (%s) from OpenStack LB pool (%s): %s", member.ID, d.Id(), err)
   295  					}
   296  					log.Printf("[DEBUG] Deleted member (%s) from pool (%s)", member.ID, d.Id())
   297  				}
   298  				return true, nil
   299  			})
   300  		}
   301  
   302  		for _, m := range membersToAdd.List() {
   303  			createOpts := resourcePoolMemberV1(d, m)
   304  			newMember, err := members.Create(networkingClient, createOpts).Extract()
   305  			if err != nil {
   306  				return fmt.Errorf("Error creating LB member: %s", err)
   307  			}
   308  			log.Printf("[DEBUG] Created member (%s) in OpenStack LB pool (%s)", newMember.ID, d.Id())
   309  		}
   310  	}
   311  
   312  	return resourceLBPoolV1Read(d, meta)
   313  }
   314  
   315  func resourceLBPoolV1Delete(d *schema.ResourceData, meta interface{}) error {
   316  	config := meta.(*Config)
   317  	networkingClient, err := config.networkingV2Client(GetRegion(d))
   318  	if err != nil {
   319  		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
   320  	}
   321  
   322  	// Make sure all monitors are disassociated first
   323  	if v, ok := d.GetOk("monitor_ids"); ok {
   324  		if monitorIDList, ok := v.([]interface{}); ok {
   325  			for _, monitorID := range monitorIDList {
   326  				mID := monitorID.(string)
   327  				log.Printf("[DEBUG] Attempting to disassociate monitor %s from pool %s", mID, d.Id())
   328  				if res := pools.DisassociateMonitor(networkingClient, d.Id(), mID); res.Err != nil {
   329  					return fmt.Errorf("Error disassociating monitor %s from pool %s: %s", mID, d.Id(), err)
   330  				}
   331  			}
   332  		}
   333  	}
   334  
   335  	stateConf := &resource.StateChangeConf{
   336  		Pending:    []string{"ACTIVE", "PENDING_DELETE"},
   337  		Target:     []string{"DELETED"},
   338  		Refresh:    waitForLBPoolDelete(networkingClient, d.Id()),
   339  		Timeout:    d.Timeout(schema.TimeoutDelete),
   340  		Delay:      5 * time.Second,
   341  		MinTimeout: 3 * time.Second,
   342  	}
   343  
   344  	_, err = stateConf.WaitForState()
   345  	if err != nil {
   346  		return fmt.Errorf("Error deleting OpenStack LB Pool: %s", err)
   347  	}
   348  
   349  	d.SetId("")
   350  	return nil
   351  }
   352  
   353  func resourcePoolMonitorIDsV1(d *schema.ResourceData) []string {
   354  	mIDsRaw := d.Get("monitor_ids").(*schema.Set)
   355  	mIDs := make([]string, mIDsRaw.Len())
   356  	for i, raw := range mIDsRaw.List() {
   357  		mIDs[i] = raw.(string)
   358  	}
   359  	return mIDs
   360  }
   361  
   362  func resourcePoolMembersV1(d *schema.ResourceData) []members.CreateOpts {
   363  	memberOptsRaw := d.Get("member").(*schema.Set)
   364  	memberOpts := make([]members.CreateOpts, memberOptsRaw.Len())
   365  	for i, raw := range memberOptsRaw.List() {
   366  		rawMap := raw.(map[string]interface{})
   367  		memberOpts[i] = members.CreateOpts{
   368  			TenantID:     rawMap["tenant_id"].(string),
   369  			Address:      rawMap["address"].(string),
   370  			ProtocolPort: rawMap["port"].(int),
   371  			PoolID:       d.Id(),
   372  		}
   373  	}
   374  	return memberOpts
   375  }
   376  
   377  func resourcePoolMemberV1(d *schema.ResourceData, raw interface{}) members.CreateOpts {
   378  	rawMap := raw.(map[string]interface{})
   379  	return members.CreateOpts{
   380  		TenantID:     rawMap["tenant_id"].(string),
   381  		Address:      rawMap["address"].(string),
   382  		ProtocolPort: rawMap["port"].(int),
   383  		PoolID:       d.Id(),
   384  	}
   385  }
   386  
   387  func resourceLBMemberV1Hash(v interface{}) int {
   388  	var buf bytes.Buffer
   389  	m := v.(map[string]interface{})
   390  	buf.WriteString(fmt.Sprintf("%s-", m["region"].(string)))
   391  	buf.WriteString(fmt.Sprintf("%s-", m["tenant_id"].(string)))
   392  	buf.WriteString(fmt.Sprintf("%s-", m["address"].(string)))
   393  	buf.WriteString(fmt.Sprintf("%d-", m["port"].(int)))
   394  
   395  	return hashcode.String(buf.String())
   396  }
   397  
   398  func resourceLBPoolV1DetermineProtocol(v string) pools.LBProtocol {
   399  	var protocol pools.LBProtocol
   400  	switch v {
   401  	case "TCP":
   402  		protocol = pools.ProtocolTCP
   403  	case "HTTP":
   404  		protocol = pools.ProtocolHTTP
   405  	case "HTTPS":
   406  		protocol = pools.ProtocolHTTPS
   407  	}
   408  
   409  	return protocol
   410  }
   411  
   412  func resourceLBPoolV1DetermineLBMethod(v string) pools.LBMethod {
   413  	var lbMethod pools.LBMethod
   414  	switch v {
   415  	case "ROUND_ROBIN":
   416  		lbMethod = pools.LBMethodRoundRobin
   417  	case "LEAST_CONNECTIONS":
   418  		lbMethod = pools.LBMethodLeastConnections
   419  	}
   420  
   421  	return lbMethod
   422  }
   423  
   424  func waitForLBPoolActive(networkingClient *gophercloud.ServiceClient, poolId string) resource.StateRefreshFunc {
   425  	return func() (interface{}, string, error) {
   426  		p, err := pools.Get(networkingClient, poolId).Extract()
   427  		if err != nil {
   428  			return nil, "", err
   429  		}
   430  
   431  		log.Printf("[DEBUG] OpenStack LB Pool: %+v", p)
   432  		if p.Status == "ACTIVE" {
   433  			return p, "ACTIVE", nil
   434  		}
   435  
   436  		return p, p.Status, nil
   437  	}
   438  }
   439  
   440  func waitForLBPoolDelete(networkingClient *gophercloud.ServiceClient, poolId string) resource.StateRefreshFunc {
   441  	return func() (interface{}, string, error) {
   442  		log.Printf("[DEBUG] Attempting to delete OpenStack LB Pool %s", poolId)
   443  
   444  		p, err := pools.Get(networkingClient, poolId).Extract()
   445  		if err != nil {
   446  			if _, ok := err.(gophercloud.ErrDefault404); ok {
   447  				log.Printf("[DEBUG] Successfully deleted OpenStack LB Pool %s", poolId)
   448  				return p, "DELETED", nil
   449  			}
   450  			return p, "ACTIVE", err
   451  		}
   452  
   453  		log.Printf("[DEBUG] OpenStack LB Pool: %+v", p)
   454  		err = pools.Delete(networkingClient, poolId).ExtractErr()
   455  		if err != nil {
   456  			if _, ok := err.(gophercloud.ErrDefault404); ok {
   457  				log.Printf("[DEBUG] Successfully deleted OpenStack LB Pool %s", poolId)
   458  				return p, "DELETED", nil
   459  			}
   460  			return p, "ACTIVE", err
   461  		}
   462  
   463  		log.Printf("[DEBUG] OpenStack LB Pool %s still active.", poolId)
   464  		return p, "ACTIVE", nil
   465  	}
   466  
   467  }