provider/openstack: Additions to the OpenStack Port resource

This commit adds further work to the OpenStack port resource:

* Makes relevant fields computed
* Adds state change functions
* Adds acceptance tests
* Adds Documentation
This commit is contained in:
Joe Topjian 2015-10-30 12:04:24 +00:00
parent 7d11b4b7e7
commit 312d371ce9
6 changed files with 375 additions and 2 deletions

View File

@ -8,8 +8,11 @@ import (
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/openstack/networking/v2/ports"
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets"
)
@ -104,6 +107,81 @@ func TestAccNetworkingV2Network_netstack(t *testing.T) {
})
}
func TestAccNetworkingV2Network_fullstack(t *testing.T) {
region := os.Getenv(OS_REGION_NAME)
var instance servers.Server
var network networks.Network
var port ports.Port
var secgroup secgroups.SecurityGroup
var subnet subnets.Subnet
var testAccNetworkingV2Network_fullstack = fmt.Sprintf(`
resource "openstack_networking_network_v2" "foo" {
region = "%s"
name = "network_1"
admin_state_up = "true"
}
resource "openstack_networking_subnet_v2" "foo" {
region = "%s"
name = "subnet_1"
network_id = "${openstack_networking_network_v2.foo.id}"
cidr = "192.168.199.0/24"
ip_version = 4
}
resource "openstack_compute_secgroup_v2" "foo" {
region = "%s"
name = "secgroup_1"
description = "a security group"
rule {
from_port = 22
to_port = 22
ip_protocol = "tcp"
cidr = "0.0.0.0/0"
}
}
resource "openstack_networking_port_v2" "foo" {
region = "%s"
name = "port_1"
network_id = "${openstack_networking_network_v2.foo.id}"
admin_state_up = "true"
security_groups = ["${openstack_compute_secgroup_v2.foo.id}"]
depends_on = ["openstack_networking_subnet_v2.foo"]
}
resource "openstack_compute_instance_v2" "foo" {
region = "%s"
name = "terraform-test"
security_groups = ["${openstack_compute_secgroup_v2.foo.name}"]
network {
port = "${openstack_networking_port_v2.foo.id}"
}
}`, region, region, region, region, region)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckNetworkingV2NetworkDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccNetworkingV2Network_fullstack,
Check: resource.ComposeTestCheckFunc(
testAccCheckNetworkingV2NetworkExists(t, "openstack_networking_network_v2.foo", &network),
testAccCheckNetworkingV2SubnetExists(t, "openstack_networking_subnet_v2.foo", &subnet),
testAccCheckComputeV2SecGroupExists(t, "openstack_compute_secgroup_v2.foo", &secgroup),
testAccCheckNetworkingV2PortExists(t, "openstack_networking_port_v2.foo", &port),
testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance),
),
},
},
})
}
func testAccCheckNetworkingV2NetworkDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)

View File

@ -4,9 +4,13 @@ import (
"fmt"
"log"
"strconv"
"time"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/ports"
)
@ -38,26 +42,31 @@ func resourceNetworkingPortV2() *schema.Resource {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
Computed: true,
},
"mac_address": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"tenant_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"device_owner": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"security_groups": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: false,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: func(v interface{}) int {
return hashcode.String(v.(string))
@ -67,6 +76,7 @@ func resourceNetworkingPortV2() *schema.Resource {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
},
}
@ -97,6 +107,18 @@ func resourceNetworkingPortV2Create(d *schema.ResourceData, meta interface{}) er
}
log.Printf("[INFO] Network ID: %s", p.ID)
log.Printf("[DEBUG] Waiting for OpenStack Neutron Port (%s) to become available.", p.ID)
stateConf := &resource.StateChangeConf{
Target: "ACTIVE",
Refresh: waitForNetworkPortActive(networkingClient, p.ID),
Timeout: 2 * time.Minute,
Delay: 5 * time.Second,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
d.SetId(p.ID)
return resourceNetworkingPortV2Read(d, meta)
@ -174,7 +196,16 @@ func resourceNetworkingPortV2Delete(d *schema.ResourceData, meta interface{}) er
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
}
err = ports.Delete(networkingClient, d.Id()).ExtractErr()
stateConf := &resource.StateChangeConf{
Pending: []string{"ACTIVE"},
Target: "DELETED",
Refresh: waitForNetworkPortDelete(networkingClient, d.Id()),
Timeout: 2 * time.Minute,
Delay: 5 * time.Second,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("Error deleting OpenStack Neutron Network: %s", err)
}
@ -201,3 +232,52 @@ func resourcePortAdminStateUpV2(d *schema.ResourceData) *bool {
return &value
}
func waitForNetworkPortActive(networkingClient *gophercloud.ServiceClient, portId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
p, err := ports.Get(networkingClient, portId).Extract()
if err != nil {
return nil, "", err
}
log.Printf("[DEBUG] OpenStack Neutron Port: %+v", p)
if p.Status == "DOWN" || p.Status == "ACTIVE" {
return p, "ACTIVE", nil
}
return p, p.Status, nil
}
}
func waitForNetworkPortDelete(networkingClient *gophercloud.ServiceClient, portId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
log.Printf("[DEBUG] Attempting to delete OpenStack Neutron Port %s", portId)
p, err := ports.Get(networkingClient, portId).Extract()
if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
if !ok {
return p, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Port %s", portId)
return p, "DELETED", nil
}
}
err = ports.Delete(networkingClient, portId).ExtractErr()
if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
if !ok {
return p, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Port %s", portId)
return p, "DELETED", nil
}
}
log.Printf("[DEBUG] OpenStack Port %s still active.\n", portId)
return p, "ACTIVE", nil
}
}

View File

@ -0,0 +1,102 @@
package openstack
import (
"fmt"
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/openstack/networking/v2/ports"
)
func TestAccNetworkingV2Port_basic(t *testing.T) {
region := os.Getenv(OS_REGION_NAME)
var network networks.Network
var port ports.Port
var testAccNetworkingV2Port_basic = fmt.Sprintf(`
resource "openstack_networking_network_v2" "foo" {
region = "%s"
name = "network_1"
admin_state_up = "true"
}
resource "openstack_networking_port_v2" "foo" {
region = "%s"
name = "port_1"
network_id = "${openstack_networking_network_v2.foo.id}"
admin_state_up = "true"
}`, region, region)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckNetworkingV2PortDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccNetworkingV2Port_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckNetworkingV2NetworkExists(t, "openstack_networking_network_v2.foo", &network),
testAccCheckNetworkingV2PortExists(t, "openstack_networking_port_v2.foo", &port),
),
},
},
})
}
func testAccCheckNetworkingV2PortDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
if err != nil {
return fmt.Errorf("(testAccCheckNetworkingV2PortDestroy) Error creating OpenStack networking client: %s", err)
}
for _, rs := range s.RootModule().Resources {
if rs.Type != "openstack_networking_port_v2" {
continue
}
_, err := ports.Get(networkingClient, rs.Primary.ID).Extract()
if err == nil {
return fmt.Errorf("Port still exists")
}
}
return nil
}
func testAccCheckNetworkingV2PortExists(t *testing.T, n string, port *ports.Port) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
config := testAccProvider.Meta().(*Config)
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
if err != nil {
return fmt.Errorf("(testAccCheckNetworkingV2PortExists) Error creating OpenStack networking client: %s", err)
}
found, err := ports.Get(networkingClient, rs.Primary.ID).Extract()
if err != nil {
return err
}
if found.ID != rs.Primary.ID {
return fmt.Errorf("Port not found")
}
*port = *found
return nil
}
}

View File

@ -14,9 +14,46 @@ Manages a V2 Neutron network resource within OpenStack.
```
resource "openstack_networking_network_v2" "network_1" {
name = "tf_test_network"
name = "network_1"
admin_state_up = "true"
}
resource "openstack_networking_subnet_v2" "subnet_1" {
name = "subnet_1"
network_id = "${openstack_networking_network_v2.network_1.id}"
cidr = "192.168.199.0/24"
ip_version = 4
}
resource "openstack_compute_secgroup_v2" "secgroup_1" {
name = "secgroup_1"
description = "a security group"
rule {
from_port = 22
to_port = 22
ip_protocol = "tcp"
cidr = "0.0.0.0/0"
}
}
resource "openstack_networking_port_v2" "port_1" {
name = "port_1"
network_id = "${openstack_networking_network_v2.network_1.id}"
admin_state_up = "true"
security_groups = ["${openstack_compute_secgroup_v2.secgroup_1.id}"]
depends_on = ["openstack_networking_subnet_v2.subnet_1"]
}
resource "openstack_compute_instance_v2" "instance_1" {
name = "instance_1"
security_groups = ["${openstack_compute_secgroup_v2.secgroup_1.name}"]
network {
port = "${openstack_networking_port_v2.port_1.id}"
}
}
```
## Argument Reference

View File

@ -0,0 +1,73 @@
---
layout: "openstack"
page_title: "OpenStack: openstack_networking_port_v2"
sidebar_current: "docs-openstack-resource-networking-port-v2"
description: |-
Manages a V2 port resource within OpenStack.
---
# openstack\_networking\_port_v2
Manages a V2 port resource within OpenStack.
## Example Usage
```
resource "openstack_networking_network_v2" "network_1" {
name = "network_1"
admin_state_up = "true"
}
resource "openstack_networking_port_v2" "port_1" {
name = "port_1"
network_id = "${openstack_networking_network_v2.network_1.id}"
admin_state_up = "true"
}
```
## Argument Reference
The following arguments are supported:
* `region` - (Required) The region in which to obtain the V2 networking client.
A networking client is needed to create a port. If omitted, the
`OS_REGION_NAME` environment variable is used. Changing this creates a new
port.
* `name` - (Optional) A unique name for the port. Changing this
updates the `name` of an existing port.
* `network_id` - (Required) The ID of the network to attach the port to. Changing
this creates a new port.
* `admin_state_up` - (Optional) Administrative up/down status for the port
(must be "true" or "false" if provided). Changing this updates the
`admin_state_up` of an existing port.
* `mac_address` - (Optional) Specify a specific MAC address for the port. Changing
this creates a new port.
* `tenant_id` - (Optional) The owner of the Port. Required if admin wants
to create a port for another tenant. Changing this creates a new port.
* `device_owner` - (Optional) The device owner of the Port. Changing this creates
a new port.
* `security_groups` - (Optional) A list of security groups to apply to the port.
The security groups must be specified by ID and not name (as opposed to how
they are configured with the Compute Instance).
* `device_id` - (Optional) The ID of the device attached to the port. Changing this
creates a new port.
## Attributes Reference
The following attributes are exported:
* `region` - See Argument Reference above.
* `admin_state_up` - See Argument Reference above.
* `mac_address` - See Argument Reference above.
* `tenant_id` - See Argument Reference above.
* `device_owner` - See Argument Reference above.
* `security_groups` - See Argument Reference above.
* `device_id` - See Argument Reference above.

View File

@ -49,6 +49,9 @@
<li<%= sidebar_current("docs-openstack-resource-networking-network-v2") %>>
<a href="/docs/providers/openstack/r/networking_network_v2.html">openstack_networking_network_v2</a>
</li>
<li<%= sidebar_current("docs-openstack-resource-networking-port-v2") %>>
<a href="/docs/providers/openstack/r/networking_port_v2.html">openstack_networking_port_v2</a>
</li>
<li<%= sidebar_current("docs-openstack-resource-networking-router-interface-v2") %>>
<a href="/docs/providers/openstack/r/networking_router_interface_v2.html">openstack_networking_router_interface_v2</a>
</li>