github.com/daveadams/terraform@v0.6.4-0.20160830094355-13ce74975936/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go (about) 1 package openstack 2 3 import ( 4 "fmt" 5 "log" 6 "time" 7 8 "github.com/hashicorp/terraform/helper/resource" 9 "github.com/hashicorp/terraform/helper/schema" 10 11 "github.com/rackspace/gophercloud" 12 "github.com/rackspace/gophercloud/openstack/networking/v2/subnets" 13 ) 14 15 func resourceNetworkingSubnetV2() *schema.Resource { 16 return &schema.Resource{ 17 Create: resourceNetworkingSubnetV2Create, 18 Read: resourceNetworkingSubnetV2Read, 19 Update: resourceNetworkingSubnetV2Update, 20 Delete: resourceNetworkingSubnetV2Delete, 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 "network_id": &schema.Schema{ 33 Type: schema.TypeString, 34 Required: true, 35 ForceNew: true, 36 }, 37 "cidr": &schema.Schema{ 38 Type: schema.TypeString, 39 Required: true, 40 ForceNew: true, 41 }, 42 "name": &schema.Schema{ 43 Type: schema.TypeString, 44 Optional: true, 45 ForceNew: false, 46 }, 47 "tenant_id": &schema.Schema{ 48 Type: schema.TypeString, 49 Optional: true, 50 ForceNew: true, 51 Computed: true, 52 }, 53 "allocation_pools": &schema.Schema{ 54 Type: schema.TypeList, 55 Optional: true, 56 ForceNew: true, 57 Elem: &schema.Resource{ 58 Schema: map[string]*schema.Schema{ 59 "start": &schema.Schema{ 60 Type: schema.TypeString, 61 Required: true, 62 }, 63 "end": &schema.Schema{ 64 Type: schema.TypeString, 65 Required: true, 66 }, 67 }, 68 }, 69 }, 70 "gateway_ip": &schema.Schema{ 71 Type: schema.TypeString, 72 Optional: true, 73 ForceNew: false, 74 Computed: true, 75 }, 76 "no_gateway": &schema.Schema{ 77 Type: schema.TypeBool, 78 Optional: true, 79 ForceNew: false, 80 }, 81 "ip_version": &schema.Schema{ 82 Type: schema.TypeInt, 83 Optional: true, 84 Default: 4, 85 ForceNew: true, 86 }, 87 "enable_dhcp": &schema.Schema{ 88 Type: schema.TypeBool, 89 Optional: true, 90 ForceNew: false, 91 Default: true, 92 }, 93 "dns_nameservers": &schema.Schema{ 94 Type: schema.TypeSet, 95 Optional: true, 96 ForceNew: false, 97 Elem: &schema.Schema{Type: schema.TypeString}, 98 Set: schema.HashString, 99 }, 100 "host_routes": &schema.Schema{ 101 Type: schema.TypeList, 102 Optional: true, 103 ForceNew: false, 104 Elem: &schema.Resource{ 105 Schema: map[string]*schema.Schema{ 106 "destination_cidr": &schema.Schema{ 107 Type: schema.TypeString, 108 Required: true, 109 }, 110 "next_hop": &schema.Schema{ 111 Type: schema.TypeString, 112 Required: true, 113 }, 114 }, 115 }, 116 }, 117 "value_specs": &schema.Schema{ 118 Type: schema.TypeMap, 119 Optional: true, 120 ForceNew: true, 121 }, 122 }, 123 } 124 } 125 126 // SubnetCreateOpts represents the attributes used when creating a new subnet. 127 type SubnetCreateOpts struct { 128 // Required 129 NetworkID string 130 CIDR string 131 // Optional 132 Name string 133 TenantID string 134 AllocationPools []subnets.AllocationPool 135 GatewayIP string 136 NoGateway bool 137 IPVersion int 138 EnableDHCP *bool 139 DNSNameservers []string 140 HostRoutes []subnets.HostRoute 141 ValueSpecs map[string]string 142 } 143 144 // ToSubnetCreateMap casts a CreateOpts struct to a map. 145 func (opts SubnetCreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) { 146 s := make(map[string]interface{}) 147 148 if opts.NetworkID == "" { 149 return nil, fmt.Errorf("A network ID is required") 150 } 151 if opts.CIDR == "" { 152 return nil, fmt.Errorf("A valid CIDR is required") 153 } 154 if opts.IPVersion != 0 && opts.IPVersion != subnets.IPv4 && opts.IPVersion != subnets.IPv6 { 155 return nil, fmt.Errorf("An IP type must either be 4 or 6") 156 } 157 158 // Both GatewayIP and NoGateway should not be set 159 if opts.GatewayIP != "" && opts.NoGateway { 160 return nil, fmt.Errorf("Both disabling the gateway and specifying a gateway is not allowed") 161 } 162 163 s["network_id"] = opts.NetworkID 164 s["cidr"] = opts.CIDR 165 166 if opts.EnableDHCP != nil { 167 s["enable_dhcp"] = &opts.EnableDHCP 168 } 169 if opts.Name != "" { 170 s["name"] = opts.Name 171 } 172 if opts.GatewayIP != "" { 173 s["gateway_ip"] = opts.GatewayIP 174 } else if opts.NoGateway { 175 s["gateway_ip"] = nil 176 } 177 if opts.TenantID != "" { 178 s["tenant_id"] = opts.TenantID 179 } 180 if opts.IPVersion != 0 { 181 s["ip_version"] = opts.IPVersion 182 } 183 if len(opts.AllocationPools) != 0 { 184 s["allocation_pools"] = opts.AllocationPools 185 } 186 if len(opts.DNSNameservers) != 0 { 187 s["dns_nameservers"] = opts.DNSNameservers 188 } 189 if len(opts.HostRoutes) != 0 { 190 s["host_routes"] = opts.HostRoutes 191 } 192 193 if opts.ValueSpecs != nil { 194 for k, v := range opts.ValueSpecs { 195 s[k] = v 196 } 197 } 198 199 return map[string]interface{}{"subnet": s}, nil 200 } 201 202 func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{}) error { 203 config := meta.(*Config) 204 networkingClient, err := config.networkingV2Client(d.Get("region").(string)) 205 if err != nil { 206 return fmt.Errorf("Error creating OpenStack networking client: %s", err) 207 } 208 209 if _, ok := d.GetOk("gateway_ip"); ok { 210 if _, ok2 := d.GetOk("no_gateway"); ok2 { 211 return fmt.Errorf("Both gateway_ip and no_gateway cannot be set.") 212 } 213 } 214 215 enableDHCP := d.Get("enable_dhcp").(bool) 216 217 createOpts := SubnetCreateOpts{ 218 NetworkID: d.Get("network_id").(string), 219 CIDR: d.Get("cidr").(string), 220 Name: d.Get("name").(string), 221 TenantID: d.Get("tenant_id").(string), 222 AllocationPools: resourceSubnetAllocationPoolsV2(d), 223 GatewayIP: d.Get("gateway_ip").(string), 224 NoGateway: d.Get("no_gateway").(bool), 225 IPVersion: d.Get("ip_version").(int), 226 DNSNameservers: resourceSubnetDNSNameserversV2(d), 227 HostRoutes: resourceSubnetHostRoutesV2(d), 228 EnableDHCP: &enableDHCP, 229 ValueSpecs: subnetValueSpecs(d), 230 } 231 232 log.Printf("[DEBUG] Create Options: %#v", createOpts) 233 s, err := subnets.Create(networkingClient, createOpts).Extract() 234 if err != nil { 235 return fmt.Errorf("Error creating OpenStack Neutron subnet: %s", err) 236 } 237 log.Printf("[INFO] Subnet ID: %s", s.ID) 238 239 log.Printf("[DEBUG] Waiting for Subnet (%s) to become available", s.ID) 240 stateConf := &resource.StateChangeConf{ 241 Target: []string{"ACTIVE"}, 242 Refresh: waitForSubnetActive(networkingClient, s.ID), 243 Timeout: 10 * time.Minute, 244 Delay: 5 * time.Second, 245 MinTimeout: 3 * time.Second, 246 } 247 248 _, err = stateConf.WaitForState() 249 250 d.SetId(s.ID) 251 252 return resourceNetworkingSubnetV2Read(d, meta) 253 } 254 255 func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) error { 256 config := meta.(*Config) 257 networkingClient, err := config.networkingV2Client(d.Get("region").(string)) 258 if err != nil { 259 return fmt.Errorf("Error creating OpenStack networking client: %s", err) 260 } 261 262 s, err := subnets.Get(networkingClient, d.Id()).Extract() 263 if err != nil { 264 return CheckDeleted(d, err, "subnet") 265 } 266 267 log.Printf("[DEBUG] Retreived Subnet %s: %+v", d.Id(), s) 268 269 d.Set("newtork_id", s.NetworkID) 270 d.Set("cidr", s.CIDR) 271 d.Set("ip_version", s.IPVersion) 272 d.Set("name", s.Name) 273 d.Set("tenant_id", s.TenantID) 274 d.Set("allocation_pools", s.AllocationPools) 275 d.Set("gateway_ip", s.GatewayIP) 276 d.Set("dns_nameservers", s.DNSNameservers) 277 d.Set("host_routes", s.HostRoutes) 278 d.Set("enable_dhcp", s.EnableDHCP) 279 d.Set("network_id", s.NetworkID) 280 281 return nil 282 } 283 284 func resourceNetworkingSubnetV2Update(d *schema.ResourceData, meta interface{}) error { 285 config := meta.(*Config) 286 networkingClient, err := config.networkingV2Client(d.Get("region").(string)) 287 if err != nil { 288 return fmt.Errorf("Error creating OpenStack networking client: %s", err) 289 } 290 291 // Check if both gateway_ip and no_gateway are set 292 if _, ok := d.GetOk("gateway_ip"); ok { 293 if _, ok2 := d.GetOk("no_gateway"); ok2 { 294 return fmt.Errorf("Both gateway_ip and no_gateway cannot be set.") 295 } 296 } 297 298 var updateOpts subnets.UpdateOpts 299 300 if d.HasChange("name") { 301 updateOpts.Name = d.Get("name").(string) 302 } 303 304 if d.HasChange("gateway_ip") { 305 updateOpts.GatewayIP = d.Get("gateway_ip").(string) 306 } 307 308 if d.HasChange("no_gateway") { 309 updateOpts.NoGateway = d.Get("no_gateway").(bool) 310 } 311 312 if d.HasChange("dns_nameservers") { 313 updateOpts.DNSNameservers = resourceSubnetDNSNameserversV2(d) 314 } 315 316 if d.HasChange("host_routes") { 317 updateOpts.HostRoutes = resourceSubnetHostRoutesV2(d) 318 } 319 320 if d.HasChange("enable_dhcp") { 321 v := d.Get("enable_dhcp").(bool) 322 updateOpts.EnableDHCP = &v 323 } 324 325 log.Printf("[DEBUG] Updating Subnet %s with options: %+v", d.Id(), updateOpts) 326 327 _, err = subnets.Update(networkingClient, d.Id(), updateOpts).Extract() 328 if err != nil { 329 return fmt.Errorf("Error updating OpenStack Neutron Subnet: %s", err) 330 } 331 332 return resourceNetworkingSubnetV2Read(d, meta) 333 } 334 335 func resourceNetworkingSubnetV2Delete(d *schema.ResourceData, meta interface{}) error { 336 config := meta.(*Config) 337 networkingClient, err := config.networkingV2Client(d.Get("region").(string)) 338 if err != nil { 339 return fmt.Errorf("Error creating OpenStack networking client: %s", err) 340 } 341 342 stateConf := &resource.StateChangeConf{ 343 Pending: []string{"ACTIVE"}, 344 Target: []string{"DELETED"}, 345 Refresh: waitForSubnetDelete(networkingClient, d.Id()), 346 Timeout: 10 * time.Minute, 347 Delay: 5 * time.Second, 348 MinTimeout: 3 * time.Second, 349 } 350 351 _, err = stateConf.WaitForState() 352 if err != nil { 353 return fmt.Errorf("Error deleting OpenStack Neutron Subnet: %s", err) 354 } 355 356 d.SetId("") 357 return nil 358 } 359 360 func resourceSubnetAllocationPoolsV2(d *schema.ResourceData) []subnets.AllocationPool { 361 rawAPs := d.Get("allocation_pools").([]interface{}) 362 aps := make([]subnets.AllocationPool, len(rawAPs)) 363 for i, raw := range rawAPs { 364 rawMap := raw.(map[string]interface{}) 365 aps[i] = subnets.AllocationPool{ 366 Start: rawMap["start"].(string), 367 End: rawMap["end"].(string), 368 } 369 } 370 return aps 371 } 372 373 func resourceSubnetDNSNameserversV2(d *schema.ResourceData) []string { 374 rawDNSN := d.Get("dns_nameservers").(*schema.Set) 375 dnsn := make([]string, rawDNSN.Len()) 376 for i, raw := range rawDNSN.List() { 377 dnsn[i] = raw.(string) 378 } 379 return dnsn 380 } 381 382 func resourceSubnetHostRoutesV2(d *schema.ResourceData) []subnets.HostRoute { 383 rawHR := d.Get("host_routes").([]interface{}) 384 hr := make([]subnets.HostRoute, len(rawHR)) 385 for i, raw := range rawHR { 386 rawMap := raw.(map[string]interface{}) 387 hr[i] = subnets.HostRoute{ 388 DestinationCIDR: rawMap["destination_cidr"].(string), 389 NextHop: rawMap["next_hop"].(string), 390 } 391 } 392 return hr 393 } 394 395 func waitForSubnetActive(networkingClient *gophercloud.ServiceClient, subnetId string) resource.StateRefreshFunc { 396 return func() (interface{}, string, error) { 397 s, err := subnets.Get(networkingClient, subnetId).Extract() 398 if err != nil { 399 return nil, "", err 400 } 401 402 log.Printf("[DEBUG] OpenStack Neutron Subnet: %+v", s) 403 return s, "ACTIVE", nil 404 } 405 } 406 407 func waitForSubnetDelete(networkingClient *gophercloud.ServiceClient, subnetId string) resource.StateRefreshFunc { 408 return func() (interface{}, string, error) { 409 log.Printf("[DEBUG] Attempting to delete OpenStack Subnet %s.\n", subnetId) 410 411 s, err := subnets.Get(networkingClient, subnetId).Extract() 412 if err != nil { 413 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 414 if !ok { 415 return s, "ACTIVE", err 416 } 417 if errCode.Actual == 404 { 418 log.Printf("[DEBUG] Successfully deleted OpenStack Subnet %s", subnetId) 419 return s, "DELETED", nil 420 } 421 } 422 423 err = subnets.Delete(networkingClient, subnetId).ExtractErr() 424 if err != nil { 425 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 426 if !ok { 427 return s, "ACTIVE", err 428 } 429 if errCode.Actual == 404 { 430 log.Printf("[DEBUG] Successfully deleted OpenStack Subnet %s", subnetId) 431 return s, "DELETED", nil 432 } 433 } 434 435 log.Printf("[DEBUG] OpenStack Subnet %s still active.\n", subnetId) 436 return s, "ACTIVE", nil 437 } 438 } 439 440 func subnetValueSpecs(d *schema.ResourceData) map[string]string { 441 m := make(map[string]string) 442 for key, val := range d.Get("value_specs").(map[string]interface{}) { 443 m[key] = val.(string) 444 } 445 return m 446 }