From f76112f4da02b12fe55648694bdedbe3984e1e81 Mon Sep 17 00:00:00 2001 From: dkalleg Date: Fri, 3 Jun 2016 19:12:35 -0700 Subject: [PATCH] OpenStack LBaaS V2 support - Monitor Resource CRUD, tests, and docs for managing a Monitor resource --- .../resource_openstack_lbaas_monitor_v2.go | 293 ++++++++++++++++++ ...esource_openstack_lbaas_monitor_v2_test.go | 176 +++++++++++ .../openstack/r/lb_monitor_v2.html.markdown | 81 +++++ 3 files changed, 550 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_lbaas_monitor_v2.go create mode 100644 builtin/providers/openstack/resource_openstack_lbaas_monitor_v2_test.go create mode 100644 website/source/docs/providers/openstack/r/lb_monitor_v2.html.markdown diff --git a/builtin/providers/openstack/resource_openstack_lbaas_monitor_v2.go b/builtin/providers/openstack/resource_openstack_lbaas_monitor_v2.go new file mode 100644 index 000000000000..48aaff8b9389 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lbaas_monitor_v2.go @@ -0,0 +1,293 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" +) + +func resourceMonitorV2() *schema.Resource { + return &schema.Resource{ + Create: resourceMonitorV2Create, + Read: resourceMonitorV2Read, + Delete: resourceMonitorV2Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + + "pool_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "delay": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "timeout": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "max_retries": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "url_path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "http_method": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "expected_codes": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "admin_state_up": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceMonitorV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + adminStateUp := d.Get("admin_state_up").(bool) + createOpts := monitors.CreateOpts{ + PoolID: d.Get("pool_id").(string), + TenantID: d.Get("tenant_id").(string), + Type: d.Get("type").(string), + Delay: d.Get("delay").(int), + Timeout: d.Get("timeout").(int), + MaxRetries: d.Get("max_retries").(int), + URLPath: d.Get("url_path").(string), + HTTPMethod: d.Get("http_method").(string), + ExpectedCodes: d.Get("expected_codes").(string), + Name: d.Get("name").(string), + AdminStateUp: &adminStateUp, + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + monitor, err := monitors.Create(networkingClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack LBaaSV2 monitor: %s", err) + } + log.Printf("[INFO] monitor ID: %s", monitor.ID) + + log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 monitor (%s) to become available.", monitor.ID) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_CREATE"}, + Target: []string{"ACTIVE"}, + Refresh: waitForMonitorActive(networkingClient, monitor.ID), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return err + } + + d.SetId(monitor.ID) + + return resourceMonitorV2Read(d, meta) +} + +func resourceMonitorV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + monitor, err := monitors.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "LBV2 Monitor") + } + + log.Printf("[DEBUG] Retreived OpenStack LBaaSV2 Monitor %s: %+v", d.Id(), monitor) + + d.Set("id", monitor.ID) + d.Set("tenant_id", monitor.TenantID) + d.Set("type", monitor.Type) + d.Set("delay", monitor.Delay) + d.Set("timeout", monitor.Timeout) + d.Set("max_retries", monitor.MaxRetries) + d.Set("url_path", monitor.URLPath) + d.Set("http_method", monitor.HTTPMethod) + d.Set("expected_codes", monitor.ExpectedCodes) + d.Set("admin_state_up", monitor.AdminStateUp) + d.Set("name", monitor.Name) + + return nil +} + +func resourceMonitorV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + var updateOpts monitors.UpdateOpts + if d.HasChange("url_path") { + updateOpts.URLPath = d.Get("url_path").(string) + } + if d.HasChange("expected_codes") { + updateOpts.ExpectedCodes = d.Get("expected_codes").(string) + } + if d.HasChange("delay") { + updateOpts.Delay = d.Get("delay").(int) + } + if d.HasChange("timeout") { + updateOpts.Timeout = d.Get("timeout").(int) + } + if d.HasChange("max_retries") { + updateOpts.MaxRetries = d.Get("max_retries").(int) + } + if d.HasChange("admin_state_up") { + asu := d.Get("admin_state_up").(bool) + updateOpts.AdminStateUp = &asu + } + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("http_method") { + updateOpts.HTTPMethod = d.Get("http_method").(string) + } + + log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Monitor %s with options: %+v", d.Id(), updateOpts) + + _, err = monitors.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack LBaaSV2 Monitor: %s", err) + } + + return resourceMonitorV2Read(d, meta) +} + +func resourceMonitorV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE", "PENDING_DELETE"}, + Target: []string{"DELETED"}, + Refresh: waitForMonitorDelete(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 LBaaSV2 Monitor: %s", err) + } + + d.SetId("") + return nil +} + +func waitForMonitorActive(networkingClient *gophercloud.ServiceClient, monitorID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + monitor, err := monitors.Get(networkingClient, monitorID).Extract() + if err != nil { + return nil, "", err + } + + log.Printf("[DEBUG] OpenStack LBaaSV2 Monitor: %+v", monitor) + return monitor, "ACTIVE", nil + } +} + +func waitForMonitorDelete(networkingClient *gophercloud.ServiceClient, monitorID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Monitor %s", monitorID) + + monitor, err := monitors.Get(networkingClient, monitorID).Extract() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return monitor, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Monitor %s", monitorID) + return monitor, "DELETED", nil + } + } + + log.Printf("[DEBUG] Openstack LBaaSV2 Monitor: %+v", monitor) + err = monitors.Delete(networkingClient, monitorID).ExtractErr() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return monitor, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Monitor %s", monitorID) + return monitor, "DELETED", nil + } + } + + log.Printf("[DEBUG] OpenStack LBaaSV2 Monitor %s still active.", monitorID) + return monitor, "ACTIVE", nil + } +} diff --git a/builtin/providers/openstack/resource_openstack_lbaas_monitor_v2_test.go b/builtin/providers/openstack/resource_openstack_lbaas_monitor_v2_test.go new file mode 100644 index 000000000000..81076f8da10a --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lbaas_monitor_v2_test.go @@ -0,0 +1,176 @@ +package openstack + +import ( + "fmt" + "log" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" +) + +func TestAccLBV2Monitor_basic(t *testing.T) { + var monitor monitors.Monitor + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLBV2MonitorDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: TestAccLBV2MonitorConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLBV2MonitorExists(t, "openstack_lbaas_monitor_v2.monitor_1", &monitor), + ), + }, + resource.TestStep{ + Config: TestAccLBV2MonitorConfig_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_lbaas_monitor_v2.monitor_1", "name", "tf_test_monitor_updated"), + resource.TestCheckResourceAttr("openstack_lbaas_monitor_v2.monitor_1", "delay", "30"), + resource.TestCheckResourceAttr("openstack_lbaas_monitor_v2.monitor_1", "timeout", "15"), + ), + }, + }, + }) +} + +func testAccCheckLBV2MonitorDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckLBV2MonitorDestroy) Error creating OpenStack networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + log.Printf("[FINDME] rs TYPE is: %T", rs) + + if rs.Type != "openstack_lbaas_monitor_v2" { + continue + } + + log.Printf("[FINDME] rs.Primary.Attributes: %#v", rs.Primary.Attributes) + _, err := monitors.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Monitor still exists: %s", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckLBV2MonitorExists(t *testing.T, n string, monitor *monitors.Monitor) 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("(testAccCheckLBV2MonitorExists) Error creating OpenStack networking client: %s", err) + } + + found, err := monitors.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Monitor not found") + } + + *monitor = *found + + return nil + } +} + +var TestAccLBV2MonitorConfig_basic = fmt.Sprintf(` +resource "openstack_networking_network_v2" "network_1" { + name = "tf_test_network" + admin_state_up = "true" +} + +resource "openstack_networking_subnet_v2" "subnet_1" { + network_id = "${openstack_networking_network_v2.network_1.id}" + cidr = "192.168.199.0/24" + ip_version = 4 + name = "tf_test_subnet" +} + +resource "openstack_lbaas_loadbalancer_v2" "loadbalancer_1" { + vip_subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + name = "tf_test_loadbalancer_v2" +} + +resource "openstack_lbaas_listener_v2" "listener_1" { + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = "${openstack_lbaas_loadbalancer_v2.loadbalancer_1.id}" + name = "tf_test_listener" +} + +resource "openstack_lbaas_pool_v2" "pool_1" { + protocol = "HTTP" + lb_method = "ROUND_ROBIN" + listener_id = "${openstack_lbaas_listener_v2.listener_1.id}" + name = "tf_test_pool" +} + +resource "openstack_lbaas_monitor_v2" "monitor_1" { + pool_id = "${openstack_lbaas_pool_v2.pool_1.id}" + type = "PING" + delay = 20 + timeout = 10 + max_retries = 5 + name = "tf_test_monitor" +}`) + +var TestAccLBV2MonitorConfig_update = fmt.Sprintf(` +resource "openstack_networking_network_v2" "network_1" { + name = "tf_test_network" + admin_state_up = "true" +} + +resource "openstack_networking_subnet_v2" "subnet_1" { + network_id = "${openstack_networking_network_v2.network_1.id}" + cidr = "192.168.199.0/24" + ip_version = 4 + name = "tf_test_subnet" +} + +resource "openstack_lbaas_loadbalancer_v2" "loadbalancer_1" { + vip_subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + name = "tf_test_loadbalancer_v2" +} + +resource "openstack_lbaas_listener_v2" "listener_1" { + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = "${openstack_lbaas_loadbalancer_v2.loadbalancer_1.id}" + name = "tf_test_listener" +} + +resource "openstack_lbaas_pool_v2" "pool_1" { + protocol = "HTTP" + lb_method = "ROUND_ROBIN" + listener_id = "${openstack_lbaas_listener_v2.listener_1.id}" + name = "tf_test_pool" +} + +resource "openstack_lbaas_monitor_v2" "monitor_1" { + pool_id = "${openstack_lbaas_pool_v2.pool_1.id}" + type = "PING" + delay = 30 + timeout = 15 + max_retries = 10 + name = "tf_test_monitor_updated" + admin_state_up = "true" +}`) diff --git a/website/source/docs/providers/openstack/r/lb_monitor_v2.html.markdown b/website/source/docs/providers/openstack/r/lb_monitor_v2.html.markdown new file mode 100644 index 000000000000..6bc491d6ff9b --- /dev/null +++ b/website/source/docs/providers/openstack/r/lb_monitor_v2.html.markdown @@ -0,0 +1,81 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_lbaas_monitor_v2" +sidebar_current: "docs-openstack-resource-lbaas-monitor-v2" +description: |- + Manages a V2 monitor resource within OpenStack. +--- + +# openstack\_lbaas\_monitor\_v2 + +Manages a V2 monitor resource within OpenStack. + +## Example Usage + +``` +resource "openstack_lbaas_monitor_v2" "monitor_1" { + type = "PING" + delay = 20 + timeout = 10 + max_retries = 5 +} +``` + +## 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 an . If omitted, the + `OS_REGION_NAME` environment variable is used. Changing this creates a new + monitor. + +* `name` - (Optional) The Name of the Monitor. + +* `tenant_id` - (Optional) Required for admins. The UUID of the tenant who owns + the monitor. Only administrative users can specify a tenant UUID + other than their own. Changing this creates a new monitor. + +* `type` - (Required) The type of probe, which is PING, TCP, HTTP, or HTTPS, + that is sent by the load balancer to verify the member state. Changing this + creates a new monitor. + +* `delay` - (Required) The time, in seconds, between sending probes to members. + +* `timeout` - (Required) Maximum number of seconds for a monitor to wait for a + ping reply before it times out. The value must be less than the delay + value. + +* `max_retries` - (Required) Number of permissible ping failures before + changing the member's status to INACTIVE. Must be a number between 1 + and 10.. + +* `url_path` - (Optional) Required for HTTP(S) types. URI path that will be + accessed if monitor type is HTTP or HTTPS. + +* `http_method` - (Optional) Required for HTTP(S) types. The HTTP method used + for requests by the monitor. If this attribute is not specified, it + defaults to "GET". + +* `expected_codes` - (Optional) Required for HTTP(S) types. Expected HTTP codes + for a passing HTTP(S) monitor. You can either specify a single status like + "200", or a range like "200-202". + +* `admin_state_up` - (Optional) The administrative state of the monitor. + A valid value is true (UP) or false (DOWN). + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The unique ID for the monitor. +* `tenant_id` - See Argument Reference above. +* `type` - See Argument Reference above. +* `delay` - See Argument Reference above. +* `timeout` - See Argument Reference above. +* `max_retries` - See Argument Reference above. +* `url_path` - See Argument Reference above. +* `http_method` - See Argument Reference above. +* `expected_codes` - See Argument Reference above. +* `admin_state_up` - See Argument Reference above.