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 }