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

     1  package openstack
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  
     8  	"github.com/gophercloud/gophercloud"
     9  	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
    10  	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips"
    11  	"github.com/hashicorp/terraform/helper/resource"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  )
    14  
    15  func resourceLBVipV1() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceLBVipV1Create,
    18  		Read:   resourceLBVipV1Read,
    19  		Update: resourceLBVipV1Update,
    20  		Delete: resourceLBVipV1Delete,
    21  		Importer: &schema.ResourceImporter{
    22  			State: schema.ImportStatePassthrough,
    23  		},
    24  
    25  		Timeouts: &schema.ResourceTimeout{
    26  			Create: schema.DefaultTimeout(10 * time.Minute),
    27  			Delete: schema.DefaultTimeout(10 * time.Minute),
    28  		},
    29  
    30  		Schema: map[string]*schema.Schema{
    31  			"region": &schema.Schema{
    32  				Type:        schema.TypeString,
    33  				Required:    true,
    34  				ForceNew:    true,
    35  				DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
    36  			},
    37  			"name": &schema.Schema{
    38  				Type:     schema.TypeString,
    39  				Required: true,
    40  				ForceNew: false,
    41  			},
    42  			"subnet_id": &schema.Schema{
    43  				Type:     schema.TypeString,
    44  				Required: true,
    45  				ForceNew: true,
    46  			},
    47  			"protocol": &schema.Schema{
    48  				Type:     schema.TypeString,
    49  				Required: true,
    50  				ForceNew: true,
    51  			},
    52  			"port": &schema.Schema{
    53  				Type:     schema.TypeInt,
    54  				Required: true,
    55  				ForceNew: true,
    56  			},
    57  			"pool_id": &schema.Schema{
    58  				Type:     schema.TypeString,
    59  				Required: true,
    60  				ForceNew: false,
    61  			},
    62  			"tenant_id": &schema.Schema{
    63  				Type:     schema.TypeString,
    64  				Optional: true,
    65  				Computed: true,
    66  				ForceNew: true,
    67  			},
    68  			"address": &schema.Schema{
    69  				Type:     schema.TypeString,
    70  				Optional: true,
    71  				Computed: true,
    72  				ForceNew: true,
    73  			},
    74  			"description": &schema.Schema{
    75  				Type:     schema.TypeString,
    76  				Optional: true,
    77  				Computed: true,
    78  				ForceNew: false,
    79  			},
    80  			"persistence": &schema.Schema{
    81  				Type:     schema.TypeMap,
    82  				Optional: true,
    83  				ForceNew: false,
    84  			},
    85  			"conn_limit": &schema.Schema{
    86  				Type:     schema.TypeInt,
    87  				Optional: true,
    88  				Computed: true,
    89  				ForceNew: false,
    90  			},
    91  			"port_id": &schema.Schema{
    92  				Type:     schema.TypeString,
    93  				Computed: true,
    94  				ForceNew: false,
    95  			},
    96  			"floating_ip": &schema.Schema{
    97  				Type:     schema.TypeString,
    98  				Optional: true,
    99  				ForceNew: false,
   100  			},
   101  			"admin_state_up": &schema.Schema{
   102  				Type:     schema.TypeBool,
   103  				Optional: true,
   104  				Computed: true,
   105  				ForceNew: false,
   106  			},
   107  		},
   108  	}
   109  }
   110  
   111  func resourceLBVipV1Create(d *schema.ResourceData, meta interface{}) error {
   112  	config := meta.(*Config)
   113  	networkingClient, err := config.networkingV2Client(GetRegion(d))
   114  	if err != nil {
   115  		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
   116  	}
   117  
   118  	createOpts := vips.CreateOpts{
   119  		Name:         d.Get("name").(string),
   120  		SubnetID:     d.Get("subnet_id").(string),
   121  		Protocol:     d.Get("protocol").(string),
   122  		ProtocolPort: d.Get("port").(int),
   123  		PoolID:       d.Get("pool_id").(string),
   124  		TenantID:     d.Get("tenant_id").(string),
   125  		Address:      d.Get("address").(string),
   126  		Description:  d.Get("description").(string),
   127  		Persistence:  resourceVipPersistenceV1(d),
   128  		ConnLimit:    gophercloud.MaybeInt(d.Get("conn_limit").(int)),
   129  	}
   130  
   131  	asu := d.Get("admin_state_up").(bool)
   132  	createOpts.AdminStateUp = &asu
   133  
   134  	log.Printf("[DEBUG] Create Options: %#v", createOpts)
   135  	p, err := vips.Create(networkingClient, createOpts).Extract()
   136  	if err != nil {
   137  		return fmt.Errorf("Error creating OpenStack LB VIP: %s", err)
   138  	}
   139  	log.Printf("[INFO] LB VIP ID: %s", p.ID)
   140  
   141  	log.Printf("[DEBUG] Waiting for OpenStack LB VIP (%s) to become available.", p.ID)
   142  
   143  	stateConf := &resource.StateChangeConf{
   144  		Pending:    []string{"PENDING_CREATE"},
   145  		Target:     []string{"ACTIVE"},
   146  		Refresh:    waitForLBVIPActive(networkingClient, p.ID),
   147  		Timeout:    d.Timeout(schema.TimeoutCreate),
   148  		Delay:      5 * time.Second,
   149  		MinTimeout: 3 * time.Second,
   150  	}
   151  
   152  	_, err = stateConf.WaitForState()
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	floatingIP := d.Get("floating_ip").(string)
   158  	if floatingIP != "" {
   159  		lbVipV1AssignFloatingIP(floatingIP, p.PortID, networkingClient)
   160  	}
   161  
   162  	d.SetId(p.ID)
   163  
   164  	return resourceLBVipV1Read(d, meta)
   165  }
   166  
   167  func resourceLBVipV1Read(d *schema.ResourceData, meta interface{}) error {
   168  	config := meta.(*Config)
   169  	networkingClient, err := config.networkingV2Client(GetRegion(d))
   170  	if err != nil {
   171  		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
   172  	}
   173  
   174  	p, err := vips.Get(networkingClient, d.Id()).Extract()
   175  	if err != nil {
   176  		return CheckDeleted(d, err, "LB VIP")
   177  	}
   178  
   179  	log.Printf("[DEBUG] Retrieved OpenStack LB VIP %s: %+v", d.Id(), p)
   180  
   181  	d.Set("name", p.Name)
   182  	d.Set("subnet_id", p.SubnetID)
   183  	d.Set("protocol", p.Protocol)
   184  	d.Set("port", p.ProtocolPort)
   185  	d.Set("pool_id", p.PoolID)
   186  	d.Set("port_id", p.PortID)
   187  	d.Set("tenant_id", p.TenantID)
   188  	d.Set("address", p.Address)
   189  	d.Set("description", p.Description)
   190  	d.Set("conn_limit", p.ConnLimit)
   191  	d.Set("admin_state_up", p.AdminStateUp)
   192  
   193  	// Set the persistence method being used
   194  	persistence := make(map[string]interface{})
   195  	if p.Persistence.Type != "" {
   196  		persistence["type"] = p.Persistence.Type
   197  	}
   198  	if p.Persistence.CookieName != "" {
   199  		persistence["cookie_name"] = p.Persistence.CookieName
   200  	}
   201  	d.Set("persistence", persistence)
   202  
   203  	d.Set("region", GetRegion(d))
   204  
   205  	return nil
   206  }
   207  
   208  func resourceLBVipV1Update(d *schema.ResourceData, meta interface{}) error {
   209  	config := meta.(*Config)
   210  	networkingClient, err := config.networkingV2Client(GetRegion(d))
   211  	if err != nil {
   212  		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
   213  	}
   214  
   215  	var updateOpts vips.UpdateOpts
   216  	if d.HasChange("name") {
   217  		v := d.Get("name").(string)
   218  		updateOpts.Name = &v
   219  	}
   220  
   221  	if d.HasChange("pool_id") {
   222  		v := d.Get("pool_id").(string)
   223  		updateOpts.PoolID = &v
   224  	}
   225  
   226  	if d.HasChange("description") {
   227  		v := d.Get("description").(string)
   228  		updateOpts.Description = &v
   229  	}
   230  
   231  	if d.HasChange("conn_limit") {
   232  		updateOpts.ConnLimit = gophercloud.MaybeInt(d.Get("conn_limit").(int))
   233  	}
   234  
   235  	if d.HasChange("floating_ip") {
   236  		portID := d.Get("port_id").(string)
   237  
   238  		// Searching for a floating IP assigned to the VIP
   239  		listOpts := floatingips.ListOpts{
   240  			PortID: portID,
   241  		}
   242  		page, err := floatingips.List(networkingClient, listOpts).AllPages()
   243  		if err != nil {
   244  			return err
   245  		}
   246  
   247  		fips, err := floatingips.ExtractFloatingIPs(page)
   248  		if err != nil {
   249  			return err
   250  		}
   251  
   252  		// If a floating IP is found we unassign it
   253  		if len(fips) == 1 {
   254  			portID := ""
   255  			updateOpts := floatingips.UpdateOpts{
   256  				PortID: &portID,
   257  			}
   258  			if err = floatingips.Update(networkingClient, fips[0].ID, updateOpts).Err; err != nil {
   259  				return err
   260  			}
   261  		}
   262  
   263  		// Assign the updated floating IP
   264  		floatingIP := d.Get("floating_ip").(string)
   265  		if floatingIP != "" {
   266  			lbVipV1AssignFloatingIP(floatingIP, portID, networkingClient)
   267  		}
   268  	}
   269  
   270  	if d.HasChange("admin_state_up") {
   271  		asu := d.Get("admin_state_up").(bool)
   272  		updateOpts.AdminStateUp = &asu
   273  	}
   274  
   275  	// Persistence has to be included, even if it hasn't changed.
   276  	updateOpts.Persistence = resourceVipPersistenceV1(d)
   277  
   278  	log.Printf("[DEBUG] Updating OpenStack LB VIP %s with options: %+v", d.Id(), updateOpts)
   279  
   280  	_, err = vips.Update(networkingClient, d.Id(), updateOpts).Extract()
   281  	if err != nil {
   282  		return fmt.Errorf("Error updating OpenStack LB VIP: %s", err)
   283  	}
   284  
   285  	return resourceLBVipV1Read(d, meta)
   286  }
   287  
   288  func resourceLBVipV1Delete(d *schema.ResourceData, meta interface{}) error {
   289  	config := meta.(*Config)
   290  	networkingClient, err := config.networkingV2Client(GetRegion(d))
   291  	if err != nil {
   292  		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
   293  	}
   294  
   295  	stateConf := &resource.StateChangeConf{
   296  		Pending:    []string{"ACTIVE", "PENDING_DELETE"},
   297  		Target:     []string{"DELETED"},
   298  		Refresh:    waitForLBVIPDelete(networkingClient, d.Id()),
   299  		Timeout:    d.Timeout(schema.TimeoutDelete),
   300  		Delay:      5 * time.Second,
   301  		MinTimeout: 3 * time.Second,
   302  	}
   303  
   304  	_, err = stateConf.WaitForState()
   305  	if err != nil {
   306  		return fmt.Errorf("Error deleting OpenStack LB VIP: %s", err)
   307  	}
   308  
   309  	d.SetId("")
   310  	return nil
   311  }
   312  
   313  func resourceVipPersistenceV1(d *schema.ResourceData) *vips.SessionPersistence {
   314  	rawP := d.Get("persistence").(interface{})
   315  	rawMap := rawP.(map[string]interface{})
   316  	if len(rawMap) != 0 {
   317  		p := vips.SessionPersistence{}
   318  		if t, ok := rawMap["type"]; ok {
   319  			p.Type = t.(string)
   320  		}
   321  		if c, ok := rawMap["cookie_name"]; ok {
   322  			p.CookieName = c.(string)
   323  		}
   324  		return &p
   325  	}
   326  	return nil
   327  }
   328  
   329  func lbVipV1AssignFloatingIP(floatingIP, portID string, networkingClient *gophercloud.ServiceClient) error {
   330  	log.Printf("[DEBUG] Assigning floating IP %s to VIP %s", floatingIP, portID)
   331  
   332  	listOpts := floatingips.ListOpts{
   333  		FloatingIP: floatingIP,
   334  	}
   335  	page, err := floatingips.List(networkingClient, listOpts).AllPages()
   336  	if err != nil {
   337  		return err
   338  	}
   339  
   340  	fips, err := floatingips.ExtractFloatingIPs(page)
   341  	if err != nil {
   342  		return err
   343  	}
   344  	if len(fips) != 1 {
   345  		return fmt.Errorf("Unable to retrieve floating IP '%s'", floatingIP)
   346  	}
   347  
   348  	updateOpts := floatingips.UpdateOpts{
   349  		PortID: &portID,
   350  	}
   351  	if err = floatingips.Update(networkingClient, fips[0].ID, updateOpts).Err; err != nil {
   352  		return err
   353  	}
   354  
   355  	return nil
   356  }
   357  
   358  func waitForLBVIPActive(networkingClient *gophercloud.ServiceClient, vipId string) resource.StateRefreshFunc {
   359  	return func() (interface{}, string, error) {
   360  		p, err := vips.Get(networkingClient, vipId).Extract()
   361  		if err != nil {
   362  			return nil, "", err
   363  		}
   364  
   365  		log.Printf("[DEBUG] OpenStack LB VIP: %+v", p)
   366  		if p.Status == "ACTIVE" {
   367  			return p, "ACTIVE", nil
   368  		}
   369  
   370  		return p, p.Status, nil
   371  	}
   372  }
   373  
   374  func waitForLBVIPDelete(networkingClient *gophercloud.ServiceClient, vipId string) resource.StateRefreshFunc {
   375  	return func() (interface{}, string, error) {
   376  		log.Printf("[DEBUG] Attempting to delete OpenStack LB VIP %s", vipId)
   377  
   378  		p, err := vips.Get(networkingClient, vipId).Extract()
   379  		if err != nil {
   380  			if _, ok := err.(gophercloud.ErrDefault404); ok {
   381  				log.Printf("[DEBUG] Successfully deleted OpenStack LB VIP %s", vipId)
   382  				return p, "DELETED", nil
   383  			}
   384  			return p, "ACTIVE", err
   385  		}
   386  
   387  		log.Printf("[DEBUG] OpenStack LB VIP: %+v", p)
   388  		err = vips.Delete(networkingClient, vipId).ExtractErr()
   389  		if err != nil {
   390  			if _, ok := err.(gophercloud.ErrDefault404); ok {
   391  				log.Printf("[DEBUG] Successfully deleted OpenStack LB VIP %s", vipId)
   392  				return p, "DELETED", nil
   393  			}
   394  			return p, "ACTIVE", err
   395  		}
   396  
   397  		log.Printf("[DEBUG] OpenStack LB VIP %s still active.", vipId)
   398  		return p, "ACTIVE", nil
   399  	}
   400  
   401  }