From 312d371ce925f38f4b9a1aa64e1df45051fdc2f1 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Fri, 30 Oct 2015 12:04:24 +0000 Subject: [PATCH] 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 --- ...ce_openstack_networking_network_v2_test.go | 78 ++++++++++++++ .../resource_openstack_networking_port_v2.go | 82 +++++++++++++- ...ource_openstack_networking_port_v2_test.go | 102 ++++++++++++++++++ .../r/networking_network_v2.html.markdown | 39 ++++++- .../r/networking_port_v2.html.markdown | 73 +++++++++++++ website/source/layouts/openstack.erb | 3 + 6 files changed, 375 insertions(+), 2 deletions(-) create mode 100644 builtin/providers/openstack/resource_openstack_networking_port_v2_test.go create mode 100644 website/source/docs/providers/openstack/r/networking_port_v2.html.markdown diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go index ca09c6b98..faa007e32 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go @@ -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) diff --git a/builtin/providers/openstack/resource_openstack_networking_port_v2.go b/builtin/providers/openstack/resource_openstack_networking_port_v2.go index 9a35fc11b..701e42c05 100644 --- a/builtin/providers/openstack/resource_openstack_networking_port_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_port_v2.go @@ -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 + } +} diff --git a/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go new file mode 100644 index 000000000..edeb61901 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go @@ -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 + } +} diff --git a/website/source/docs/providers/openstack/r/networking_network_v2.html.markdown b/website/source/docs/providers/openstack/r/networking_network_v2.html.markdown index f62454e30..9a9eab935 100644 --- a/website/source/docs/providers/openstack/r/networking_network_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/networking_network_v2.html.markdown @@ -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 diff --git a/website/source/docs/providers/openstack/r/networking_port_v2.html.markdown b/website/source/docs/providers/openstack/r/networking_port_v2.html.markdown new file mode 100644 index 000000000..d10130a56 --- /dev/null +++ b/website/source/docs/providers/openstack/r/networking_port_v2.html.markdown @@ -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. diff --git a/website/source/layouts/openstack.erb b/website/source/layouts/openstack.erb index 2b92e052c..fee3fe91c 100644 --- a/website/source/layouts/openstack.erb +++ b/website/source/layouts/openstack.erb @@ -49,6 +49,9 @@ > openstack_networking_network_v2 + > + openstack_networking_port_v2 + > openstack_networking_router_interface_v2