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 }