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