github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/openstack/resource_openstack_networking_port_v2.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/ports" 15 ) 16 17 func resourceNetworkingPortV2() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceNetworkingPortV2Create, 20 Read: resourceNetworkingPortV2Read, 21 Update: resourceNetworkingPortV2Update, 22 Delete: resourceNetworkingPortV2Delete, 23 Importer: &schema.ResourceImporter{ 24 State: schema.ImportStatePassthrough, 25 }, 26 27 Timeouts: &schema.ResourceTimeout{ 28 Create: schema.DefaultTimeout(10 * time.Minute), 29 Delete: schema.DefaultTimeout(10 * time.Minute), 30 }, 31 32 Schema: map[string]*schema.Schema{ 33 "region": &schema.Schema{ 34 Type: schema.TypeString, 35 Required: true, 36 ForceNew: true, 37 DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), 38 }, 39 "name": &schema.Schema{ 40 Type: schema.TypeString, 41 Optional: true, 42 ForceNew: false, 43 }, 44 "network_id": &schema.Schema{ 45 Type: schema.TypeString, 46 Required: true, 47 ForceNew: true, 48 }, 49 "admin_state_up": &schema.Schema{ 50 Type: schema.TypeBool, 51 Optional: true, 52 ForceNew: false, 53 Computed: true, 54 }, 55 "mac_address": &schema.Schema{ 56 Type: schema.TypeString, 57 Optional: true, 58 ForceNew: true, 59 Computed: true, 60 }, 61 "tenant_id": &schema.Schema{ 62 Type: schema.TypeString, 63 Optional: true, 64 ForceNew: true, 65 Computed: true, 66 }, 67 "device_owner": &schema.Schema{ 68 Type: schema.TypeString, 69 Optional: true, 70 ForceNew: true, 71 Computed: true, 72 }, 73 "security_group_ids": &schema.Schema{ 74 Type: schema.TypeSet, 75 Optional: true, 76 ForceNew: false, 77 Computed: true, 78 Elem: &schema.Schema{Type: schema.TypeString}, 79 Set: schema.HashString, 80 }, 81 "device_id": &schema.Schema{ 82 Type: schema.TypeString, 83 Optional: true, 84 ForceNew: true, 85 Computed: true, 86 }, 87 "fixed_ip": &schema.Schema{ 88 Type: schema.TypeList, 89 Optional: true, 90 ForceNew: false, 91 Elem: &schema.Resource{ 92 Schema: map[string]*schema.Schema{ 93 "subnet_id": &schema.Schema{ 94 Type: schema.TypeString, 95 Required: true, 96 }, 97 "ip_address": &schema.Schema{ 98 Type: schema.TypeString, 99 Optional: true, 100 }, 101 }, 102 }, 103 }, 104 "allowed_address_pairs": &schema.Schema{ 105 Type: schema.TypeSet, 106 Optional: true, 107 ForceNew: false, 108 Computed: true, 109 Set: allowedAddressPairsHash, 110 Elem: &schema.Resource{ 111 Schema: map[string]*schema.Schema{ 112 "ip_address": &schema.Schema{ 113 Type: schema.TypeString, 114 Required: true, 115 }, 116 "mac_address": &schema.Schema{ 117 Type: schema.TypeString, 118 Optional: true, 119 Computed: true, 120 }, 121 }, 122 }, 123 }, 124 "value_specs": &schema.Schema{ 125 Type: schema.TypeMap, 126 Optional: true, 127 ForceNew: true, 128 }, 129 "all_fixed_ips": &schema.Schema{ 130 Type: schema.TypeList, 131 Computed: true, 132 Elem: &schema.Schema{Type: schema.TypeString}, 133 }, 134 }, 135 } 136 } 137 138 func resourceNetworkingPortV2Create(d *schema.ResourceData, meta interface{}) error { 139 config := meta.(*Config) 140 networkingClient, err := config.networkingV2Client(GetRegion(d)) 141 if err != nil { 142 return fmt.Errorf("Error creating OpenStack networking client: %s", err) 143 } 144 145 createOpts := PortCreateOpts{ 146 ports.CreateOpts{ 147 Name: d.Get("name").(string), 148 AdminStateUp: resourcePortAdminStateUpV2(d), 149 NetworkID: d.Get("network_id").(string), 150 MACAddress: d.Get("mac_address").(string), 151 TenantID: d.Get("tenant_id").(string), 152 DeviceOwner: d.Get("device_owner").(string), 153 SecurityGroups: resourcePortSecurityGroupsV2(d), 154 DeviceID: d.Get("device_id").(string), 155 FixedIPs: resourcePortFixedIpsV2(d), 156 AllowedAddressPairs: resourceAllowedAddressPairsV2(d), 157 }, 158 MapValueSpecs(d), 159 } 160 161 log.Printf("[DEBUG] Create Options: %#v", createOpts) 162 p, err := ports.Create(networkingClient, createOpts).Extract() 163 if err != nil { 164 return fmt.Errorf("Error creating OpenStack Neutron network: %s", err) 165 } 166 log.Printf("[INFO] Network ID: %s", p.ID) 167 168 log.Printf("[DEBUG] Waiting for OpenStack Neutron Port (%s) to become available.", p.ID) 169 170 stateConf := &resource.StateChangeConf{ 171 Target: []string{"ACTIVE"}, 172 Refresh: waitForNetworkPortActive(networkingClient, p.ID), 173 Timeout: d.Timeout(schema.TimeoutCreate), 174 Delay: 5 * time.Second, 175 MinTimeout: 3 * time.Second, 176 } 177 178 _, err = stateConf.WaitForState() 179 180 d.SetId(p.ID) 181 182 return resourceNetworkingPortV2Read(d, meta) 183 } 184 185 func resourceNetworkingPortV2Read(d *schema.ResourceData, meta interface{}) error { 186 config := meta.(*Config) 187 networkingClient, err := config.networkingV2Client(GetRegion(d)) 188 if err != nil { 189 return fmt.Errorf("Error creating OpenStack networking client: %s", err) 190 } 191 192 p, err := ports.Get(networkingClient, d.Id()).Extract() 193 if err != nil { 194 return CheckDeleted(d, err, "port") 195 } 196 197 log.Printf("[DEBUG] Retrieved Port %s: %+v", d.Id(), p) 198 199 d.Set("name", p.Name) 200 d.Set("admin_state_up", p.AdminStateUp) 201 d.Set("network_id", p.NetworkID) 202 d.Set("mac_address", p.MACAddress) 203 d.Set("tenant_id", p.TenantID) 204 d.Set("device_owner", p.DeviceOwner) 205 d.Set("security_group_ids", p.SecurityGroups) 206 d.Set("device_id", p.DeviceID) 207 208 // Create a slice of all returned Fixed IPs. 209 // This will be in the order returned by the API, 210 // which is usually alpha-numeric. 211 var ips []string 212 for _, ipObject := range p.FixedIPs { 213 ips = append(ips, ipObject.IPAddress) 214 } 215 d.Set("all_fixed_ips", ips) 216 217 // Convert AllowedAddressPairs to list of map 218 var pairs []map[string]interface{} 219 for _, pairObject := range p.AllowedAddressPairs { 220 pair := make(map[string]interface{}) 221 pair["ip_address"] = pairObject.IPAddress 222 pair["mac_address"] = pairObject.MACAddress 223 pairs = append(pairs, pair) 224 } 225 d.Set("allowed_address_pairs", pairs) 226 227 d.Set("region", GetRegion(d)) 228 229 return nil 230 } 231 232 func resourceNetworkingPortV2Update(d *schema.ResourceData, meta interface{}) error { 233 config := meta.(*Config) 234 networkingClient, err := config.networkingV2Client(GetRegion(d)) 235 if err != nil { 236 return fmt.Errorf("Error creating OpenStack networking client: %s", err) 237 } 238 239 // security_group_ids and allowed_address_pairs are able to send empty arrays 240 // to denote the removal of each. But their default zero-value is translated 241 // to "null", which has been reported to cause problems in vendor-modified 242 // OpenStack clouds. Therefore, we must set them in each request update. 243 updateOpts := ports.UpdateOpts{ 244 AllowedAddressPairs: resourceAllowedAddressPairsV2(d), 245 SecurityGroups: resourcePortSecurityGroupsV2(d), 246 } 247 248 if d.HasChange("name") { 249 updateOpts.Name = d.Get("name").(string) 250 } 251 252 if d.HasChange("admin_state_up") { 253 updateOpts.AdminStateUp = resourcePortAdminStateUpV2(d) 254 } 255 256 if d.HasChange("device_owner") { 257 updateOpts.DeviceOwner = d.Get("device_owner").(string) 258 } 259 260 if d.HasChange("device_id") { 261 updateOpts.DeviceID = d.Get("device_id").(string) 262 } 263 264 if d.HasChange("fixed_ip") { 265 updateOpts.FixedIPs = resourcePortFixedIpsV2(d) 266 } 267 268 log.Printf("[DEBUG] Updating Port %s with options: %+v", d.Id(), updateOpts) 269 270 _, err = ports.Update(networkingClient, d.Id(), updateOpts).Extract() 271 if err != nil { 272 return fmt.Errorf("Error updating OpenStack Neutron Network: %s", err) 273 } 274 275 return resourceNetworkingPortV2Read(d, meta) 276 } 277 278 func resourceNetworkingPortV2Delete(d *schema.ResourceData, meta interface{}) error { 279 config := meta.(*Config) 280 networkingClient, err := config.networkingV2Client(GetRegion(d)) 281 if err != nil { 282 return fmt.Errorf("Error creating OpenStack networking client: %s", err) 283 } 284 285 stateConf := &resource.StateChangeConf{ 286 Pending: []string{"ACTIVE"}, 287 Target: []string{"DELETED"}, 288 Refresh: waitForNetworkPortDelete(networkingClient, d.Id()), 289 Timeout: d.Timeout(schema.TimeoutDelete), 290 Delay: 5 * time.Second, 291 MinTimeout: 3 * time.Second, 292 } 293 294 _, err = stateConf.WaitForState() 295 if err != nil { 296 return fmt.Errorf("Error deleting OpenStack Neutron Network: %s", err) 297 } 298 299 d.SetId("") 300 return nil 301 } 302 303 func resourcePortSecurityGroupsV2(d *schema.ResourceData) []string { 304 rawSecurityGroups := d.Get("security_group_ids").(*schema.Set) 305 groups := make([]string, rawSecurityGroups.Len()) 306 for i, raw := range rawSecurityGroups.List() { 307 groups[i] = raw.(string) 308 } 309 return groups 310 } 311 312 func resourcePortFixedIpsV2(d *schema.ResourceData) interface{} { 313 rawIP := d.Get("fixed_ip").([]interface{}) 314 315 if len(rawIP) == 0 { 316 return nil 317 } 318 319 ip := make([]ports.IP, len(rawIP)) 320 for i, raw := range rawIP { 321 rawMap := raw.(map[string]interface{}) 322 ip[i] = ports.IP{ 323 SubnetID: rawMap["subnet_id"].(string), 324 IPAddress: rawMap["ip_address"].(string), 325 } 326 } 327 return ip 328 } 329 330 func resourceAllowedAddressPairsV2(d *schema.ResourceData) []ports.AddressPair { 331 // ports.AddressPair 332 rawPairs := d.Get("allowed_address_pairs").(*schema.Set).List() 333 334 pairs := make([]ports.AddressPair, len(rawPairs)) 335 for i, raw := range rawPairs { 336 rawMap := raw.(map[string]interface{}) 337 pairs[i] = ports.AddressPair{ 338 IPAddress: rawMap["ip_address"].(string), 339 MACAddress: rawMap["mac_address"].(string), 340 } 341 } 342 return pairs 343 } 344 345 func resourcePortAdminStateUpV2(d *schema.ResourceData) *bool { 346 value := false 347 348 if raw, ok := d.GetOk("admin_state_up"); ok && raw == true { 349 value = true 350 } 351 352 return &value 353 } 354 355 func allowedAddressPairsHash(v interface{}) int { 356 var buf bytes.Buffer 357 m := v.(map[string]interface{}) 358 buf.WriteString(fmt.Sprintf("%s", m["ip_address"].(string))) 359 360 return hashcode.String(buf.String()) 361 } 362 363 func waitForNetworkPortActive(networkingClient *gophercloud.ServiceClient, portId string) resource.StateRefreshFunc { 364 return func() (interface{}, string, error) { 365 p, err := ports.Get(networkingClient, portId).Extract() 366 if err != nil { 367 return nil, "", err 368 } 369 370 log.Printf("[DEBUG] OpenStack Neutron Port: %+v", p) 371 if p.Status == "DOWN" || p.Status == "ACTIVE" { 372 return p, "ACTIVE", nil 373 } 374 375 return p, p.Status, nil 376 } 377 } 378 379 func waitForNetworkPortDelete(networkingClient *gophercloud.ServiceClient, portId string) resource.StateRefreshFunc { 380 return func() (interface{}, string, error) { 381 log.Printf("[DEBUG] Attempting to delete OpenStack Neutron Port %s", portId) 382 383 p, err := ports.Get(networkingClient, portId).Extract() 384 if err != nil { 385 if _, ok := err.(gophercloud.ErrDefault404); ok { 386 log.Printf("[DEBUG] Successfully deleted OpenStack Port %s", portId) 387 return p, "DELETED", nil 388 } 389 return p, "ACTIVE", err 390 } 391 392 err = ports.Delete(networkingClient, portId).ExtractErr() 393 if err != nil { 394 if _, ok := err.(gophercloud.ErrDefault404); ok { 395 log.Printf("[DEBUG] Successfully deleted OpenStack Port %s", portId) 396 return p, "DELETED", nil 397 } 398 return p, "ACTIVE", err 399 } 400 401 log.Printf("[DEBUG] OpenStack Port %s still active.\n", portId) 402 return p, "ACTIVE", nil 403 } 404 }