diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 867691cff042..9116ec994515 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -105,6 +105,11 @@ func Provider() terraform.ResourceProvider { "openstack_lb_monitor_v1": resourceLBMonitorV1(), "openstack_lb_pool_v1": resourceLBPoolV1(), "openstack_lb_vip_v1": resourceLBVipV1(), + "openstack_lb_loadbalancer_v2": resourceLoadBalancerV2(), + "openstack_lb_listener_v2": resourceListenerV2(), + "openstack_lb_pool_v2": resourcePoolV2(), + "openstack_lb_member_v2": resourceMemberV2(), + "openstack_lb_monitor_v2": resourceMonitorV2(), "openstack_networking_network_v2": resourceNetworkingNetworkV2(), "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), "openstack_networking_floatingip_v2": resourceNetworkingFloatingIPV2(), diff --git a/builtin/providers/openstack/resource_openstack_lb_listener_v2.go b/builtin/providers/openstack/resource_openstack_lb_listener_v2.go new file mode 100644 index 000000000000..51fbd2cd2e22 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_listener_v2.go @@ -0,0 +1,313 @@ +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/listeners" +) + +func resourceListenerV2() *schema.Resource { + return &schema.Resource{ + Create: resourceListenerV2Create, + Read: resourceListenerV2Read, + Update: resourceListenerV2Update, + Delete: resourceListenerV2Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "TCP" && value != "HTTP" && value != "HTTPS" { + errors = append(errors, fmt.Errorf( + "Only 'TCP', 'HTTP', and 'HTTPS' are supported values for 'protocol'")) + } + return + }, + }, + + "protocol_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "loadbalancer_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "default_pool_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "connection_limit": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + + "default_tls_container_ref": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "sni_container_refs": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "admin_state_up": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + + "id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceListenerV2Create(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) + connLimit := d.Get("connection_limit").(int) + var sniContainerRefs []string + if raw, ok := d.GetOk("sni_container_refs"); ok { + for _, v := range raw.([]interface{}) { + sniContainerRefs = append(sniContainerRefs, v.(string)) + } + } + createOpts := listeners.CreateOpts{ + Protocol: listeners.Protocol(d.Get("protocol").(string)), + ProtocolPort: d.Get("protocol_port").(int), + TenantID: d.Get("tenant_id").(string), + LoadbalancerID: d.Get("loadbalancer_id").(string), + Name: d.Get("name").(string), + DefaultPoolID: d.Get("default_pool_id").(string), + Description: d.Get("description").(string), + ConnLimit: &connLimit, + DefaultTlsContainerRef: d.Get("default_tls_container_ref").(string), + SniContainerRefs: sniContainerRefs, + AdminStateUp: &adminStateUp, + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + listener, err := listeners.Create(networkingClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack LBaaSV2 listener: %s", err) + } + log.Printf("[INFO] Listener ID: %s", listener.ID) + + log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 listener (%s) to become available.", listener.ID) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_CREATE"}, + Target: []string{"ACTIVE"}, + Refresh: waitForListenerActive(networkingClient, listener.ID), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return err + } + + d.SetId(listener.ID) + + return resourceListenerV2Read(d, meta) +} + +func resourceListenerV2Read(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) + } + + listener, err := listeners.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "LBV2 listener") + } + + log.Printf("[DEBUG] Retreived OpenStack LBaaSV2 listener %s: %+v", d.Id(), listener) + + d.Set("id", listener.ID) + d.Set("name", listener.Name) + d.Set("protocol", listener.Protocol) + d.Set("tenant_id", listener.TenantID) + d.Set("description", listener.Description) + d.Set("protocol_port", listener.ProtocolPort) + d.Set("admin_state_up", listener.AdminStateUp) + d.Set("default_pool_id", listener.DefaultPoolID) + d.Set("connection_limit", listener.ConnLimit) + d.Set("sni_container_refs", listener.SniContainerRefs) + d.Set("default_tls_container_ref", listener.DefaultTlsContainerRef) + + return nil +} + +func resourceListenerV2Update(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 listeners.UpdateOpts + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("description") { + updateOpts.Description = d.Get("description").(string) + } + if d.HasChange("connection_limit") { + connLimit := d.Get("connection_limit").(int) + updateOpts.ConnLimit = &connLimit + } + if d.HasChange("default_tls_container_ref") { + updateOpts.DefaultTlsContainerRef = d.Get("default_tls_container_ref").(string) + } + if d.HasChange("sni_container_refs") { + var sniContainerRefs []string + if raw, ok := d.GetOk("sni_container_refs"); ok { + for _, v := range raw.([]interface{}) { + sniContainerRefs = append(sniContainerRefs, v.(string)) + } + } + updateOpts.SniContainerRefs = sniContainerRefs + } + if d.HasChange("admin_state_up") { + asu := d.Get("admin_state_up").(bool) + updateOpts.AdminStateUp = &asu + } + + log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Listener %s with options: %+v", d.Id(), updateOpts) + + _, err = listeners.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack LBaaSV2 Listener: %s", err) + } + + return resourceListenerV2Read(d, meta) + +} + +func resourceListenerV2Delete(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: waitForListenerDelete(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 listener: %s", err) + } + + d.SetId("") + return nil +} + +func waitForListenerActive(networkingClient *gophercloud.ServiceClient, listenerID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + listener, err := listeners.Get(networkingClient, listenerID).Extract() + if err != nil { + return nil, "", err + } + + // The listener resource has no Status attribute, so a successful Get is the best we can do + log.Printf("[DEBUG] OpenStack LBaaSV2 listener: %+v", listener) + return listener, "ACTIVE", nil + } +} + +func waitForListenerDelete(networkingClient *gophercloud.ServiceClient, listenerID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 listener %s", listenerID) + + listener, err := listeners.Get(networkingClient, listenerID).Extract() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return listener, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 listener %s", listenerID) + return listener, "DELETED", nil + } + } + + log.Printf("[DEBUG] Openstack LBaaSV2 listener: %+v", listener) + err = listeners.Delete(networkingClient, listenerID).ExtractErr() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return listener, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 listener %s", listenerID) + return listener, "DELETED", nil + } + } + + log.Printf("[DEBUG] OpenStack LBaaSV2 listener %s still active.", listenerID) + return listener, "ACTIVE", nil + } +} diff --git a/builtin/providers/openstack/resource_openstack_lb_listener_v2_test.go b/builtin/providers/openstack/resource_openstack_lb_listener_v2_test.go new file mode 100644 index 000000000000..64f7c9eb2be3 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_listener_v2_test.go @@ -0,0 +1,142 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" +) + +func TestAccLBV2Listener_basic(t *testing.T) { + var listener listeners.Listener + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLBV2ListenerDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: TestAccLBV2ListenerConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLBV2ListenerExists(t, "openstack_lb_listener_v2.listener_1", &listener), + ), + }, + resource.TestStep{ + Config: TestAccLBV2ListenerConfig_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_lb_listener_v2.listener_1", "name", "tf_test_listener_updated"), + resource.TestCheckResourceAttr("openstack_lb_listener_v2.listener_1", "connection_limit", "100"), + ), + }, + }, + }) +} + +func testAccCheckLBV2ListenerDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckLBV2ListenerDestroy) Error creating OpenStack networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_lb_listener_v2" { + continue + } + + _, err := listeners.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Listener still exists: %s", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckLBV2ListenerExists(t *testing.T, n string, listener *listeners.Listener) 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("(testAccCheckLBV2ListenerExists) Error creating OpenStack networking client: %s", err) + } + + found, err := listeners.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Member not found") + } + + *listener = *found + + return nil + } +} + +var TestAccLBV2ListenerConfig_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_lb_loadbalancer_v2" "loadbalancer_1" { + vip_subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + name = "tf_test_loadbalancer_v2" + } + + resource "openstack_lb_listener_v2" "listener_1" { + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = "${openstack_lb_loadbalancer_v2.loadbalancer_1.id}" + name = "tf_test_listener" + } + `) + +var TestAccLBV2ListenerConfig_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_lb_loadbalancer_v2" "loadbalancer_1" { + vip_subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + name = "tf_test_loadbalancer_v2" + } + + resource "openstack_lb_listener_v2" "listener_1" { + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = "${openstack_lb_loadbalancer_v2.loadbalancer_1.id}" + name = "tf_test_listener_updated" + connection_limit = 100 + admin_state_up = "true" + } +`) diff --git a/builtin/providers/openstack/resource_openstack_lb_loadbalancer_v2.go b/builtin/providers/openstack/resource_openstack_lb_loadbalancer_v2.go new file mode 100644 index 000000000000..c8f1ec01d15b --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_loadbalancer_v2.go @@ -0,0 +1,256 @@ +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/loadbalancers" +) + +func resourceLoadBalancerV2() *schema.Resource { + return &schema.Resource{ + Create: resourceLoadBalancerV2Create, + Read: resourceLoadBalancerV2Read, + Update: resourceLoadBalancerV2Update, + Delete: resourceLoadBalancerV2Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "vip_subnet_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "vip_address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "admin_state_up": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + + "flavor": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "provider": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func resourceLoadBalancerV2Create(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 := loadbalancers.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + VipSubnetID: d.Get("vip_subnet_id").(string), + TenantID: d.Get("tenant_id").(string), + VipAddress: d.Get("vip_address").(string), + AdminStateUp: &adminStateUp, + Flavor: d.Get("flavor").(string), + Provider: d.Get("provider").(string), + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + lb, err := loadbalancers.Create(networkingClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack LoadBalancer: %s", err) + } + log.Printf("[INFO] LoadBalancer ID: %s", lb.ID) + + log.Printf("[DEBUG] Waiting for Openstack LoadBalancer (%s) to become available.", lb.ID) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_CREATE"}, + Target: []string{"ACTIVE"}, + Refresh: waitForLoadBalancerActive(networkingClient, lb.ID), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return err + } + + d.SetId(lb.ID) + + return resourceLoadBalancerV2Read(d, meta) +} + +func resourceLoadBalancerV2Read(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) + } + + lb, err := loadbalancers.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "LoadBalancerV2") + } + + log.Printf("[DEBUG] Retreived OpenStack LoadBalancerV2 %s: %+v", d.Id(), lb) + + d.Set("name", lb.Name) + d.Set("description", lb.Description) + d.Set("vip_subnet_id", lb.VipSubnetID) + d.Set("tenant_id", lb.TenantID) + d.Set("vip_address", lb.VipAddress) + d.Set("admin_state_up", lb.AdminStateUp) + d.Set("flavor", lb.Flavor) + d.Set("provider", lb.Provider) + + return nil +} + +func resourceLoadBalancerV2Update(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 loadbalancers.UpdateOpts + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("description") { + updateOpts.Description = d.Get("description").(string) + } + if d.HasChange("admin_state_up") { + asu := d.Get("admin_state_up").(bool) + updateOpts.AdminStateUp = &asu + } + + log.Printf("[DEBUG] Updating OpenStack LBaaSV2 LoadBalancer %s with options: %+v", d.Id(), updateOpts) + + _, err = loadbalancers.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack LBaaSV2 LoadBalancer: %s", err) + } + + return resourceLoadBalancerV2Read(d, meta) +} + +func resourceLoadBalancerV2Delete(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: waitForLoadBalancerDelete(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 LB Pool: %s", err) + } + + d.SetId("") + return nil +} + +func waitForLoadBalancerActive(networkingClient *gophercloud.ServiceClient, lbID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + lb, err := loadbalancers.Get(networkingClient, lbID).Extract() + if err != nil { + return nil, "", err + } + + log.Printf("[DEBUG] OpenStack LoadBalancer: %+v", lb) + if lb.ProvisioningStatus == "ACTIVE" { + return lb, "ACTIVE", nil + } + + return lb, lb.ProvisioningStatus, nil + } +} + +func waitForLoadBalancerDelete(networkingClient *gophercloud.ServiceClient, lbID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete OpenStack LoadBalancerV2 %s", lbID) + + lb, err := loadbalancers.Get(networkingClient, lbID).Extract() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return lb, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack LoadBalancerV2 %s", lbID) + return lb, "DELETED", nil + } + } + + log.Printf("[DEBUG] Openstack LoadBalancerV2: %+v", lb) + err = loadbalancers.Delete(networkingClient, lbID).ExtractErr() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return lb, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack LoadBalancerV2 %s", lbID) + return lb, "DELETED", nil + } + } + + log.Printf("[DEBUG] OpenStack LoadBalancerV2 %s still active.", lbID) + return lb, "ACTIVE", nil + } +} diff --git a/builtin/providers/openstack/resource_openstack_lb_loadbalancer_v2_test.go b/builtin/providers/openstack/resource_openstack_lb_loadbalancer_v2_test.go new file mode 100644 index 000000000000..7762df82a4a1 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_loadbalancer_v2_test.go @@ -0,0 +1,128 @@ +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/loadbalancers" +) + +func TestAccLBV2LoadBalancer_basic(t *testing.T) { + var lb loadbalancers.LoadBalancer + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLBV2LoadBalancerDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: TestAccLBV2LoadBalancerConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLBV2LoadBalancerExists(t, "openstack_lb_loadbalancer_v2.loadbalancer_1", &lb), + ), + }, + resource.TestStep{ + Config: TestAccLBV2LoadBalancerConfig_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_lb_loadbalancer_v2.loadbalancer_1", "name", "tf_test_loadbalancer_v2_updated"), + ), + }, + }, + }) +} + +func testAccCheckLBV2LoadBalancerDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckLBV2LoadBalancerDestroy) Error creating OpenStack networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + log.Printf("[FINDME] rs TYPE is: %#v", rs.Type) + + if rs.Type != "openstack_lb_loadbalancer_v2" { + continue + } + + _, err := loadbalancers.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("LoadBalancer still exists: %s", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckLBV2LoadBalancerExists(t *testing.T, n string, lb *loadbalancers.LoadBalancer) 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("(testAccCheckLBV2LoadBalancerExists) Error creating OpenStack networking client: %s", err) + } + + found, err := loadbalancers.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Member not found") + } + + *lb = *found + + return nil + } +} + +var TestAccLBV2LoadBalancerConfig_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_lb_loadbalancer_v2" "loadbalancer_1" { + vip_subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + name = "tf_test_loadbalancer_v2" + }`) + +var TestAccLBV2LoadBalancerConfig_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_lb_loadbalancer_v2" "loadbalancer_1" { + vip_subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + name = "tf_test_loadbalancer_v2_updated" + admin_state_up = "true" + } +`) diff --git a/builtin/providers/openstack/resource_openstack_lb_member_v2.go b/builtin/providers/openstack/resource_openstack_lb_member_v2.go new file mode 100644 index 000000000000..1bb28daee17e --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_member_v2.go @@ -0,0 +1,271 @@ +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/pools" +) + +func resourceMemberV2() *schema.Resource { + return &schema.Resource{ + Create: resourceMemberV2Create, + Read: resourceMemberV2Read, + Update: resourceMemberV2Update, + Delete: resourceMemberV2Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "protocol_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "weight": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(int) + if value < 1 { + errors = append(errors, fmt.Errorf( + "Only numbers greater than 0 are supported values for 'weight'")) + } + return + }, + }, + + "subnet_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "admin_state_up": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + + "pool_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceMemberV2Create(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) + } + + subnetID := d.Get("subnet_id").(string) + adminStateUp := d.Get("admin_state_up").(bool) + createOpts := pools.MemberCreateOpts{ + Name: d.Get("name").(string), + TenantID: d.Get("tenant_id").(string), + Address: d.Get("address").(string), + ProtocolPort: d.Get("protocol_port").(int), + Weight: d.Get("weight").(int), + AdminStateUp: &adminStateUp, + } + // Must omit if not set + if subnetID != "" { + createOpts.SubnetID = subnetID + } + + poolID := d.Get("pool_id").(string) + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + member, err := pools.CreateAssociateMember(networkingClient, poolID, createOpts).ExtractMember() + if err != nil { + return fmt.Errorf("Error creating OpenStack LBaaSV2 member: %s", err) + } + log.Printf("[INFO] member ID: %s", member.ID) + + log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 member (%s) to become available.", member.ID) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_CREATE"}, + Target: []string{"ACTIVE"}, + Refresh: waitForMemberActive(networkingClient, poolID, member.ID), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return err + } + + d.SetId(member.ID) + + return resourceMemberV2Read(d, meta) +} + +func resourceMemberV2Read(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) + } + + member, err := pools.GetAssociateMember(networkingClient, d.Get("pool_id").(string), d.Id()).ExtractMember() + if err != nil { + return CheckDeleted(d, err, "LBV2 Member") + } + + log.Printf("[DEBUG] Retreived OpenStack LBaaSV2 Member %s: %+v", d.Id(), member) + + d.Set("name", member.Name) + d.Set("weight", member.Weight) + d.Set("admin_state_up", member.AdminStateUp) + d.Set("tenant_id", member.TenantID) + d.Set("subnet_id", member.SubnetID) + d.Set("address", member.Address) + d.Set("protocol_port", member.ProtocolPort) + d.Set("id", member.ID) + + return nil +} + +func resourceMemberV2Update(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 pools.MemberUpdateOpts + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("weight") { + updateOpts.Weight = d.Get("weight").(int) + } + if d.HasChange("admin_state_up") { + asu := d.Get("admin_state_up").(bool) + updateOpts.AdminStateUp = &asu + } + + log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Member %s with options: %+v", d.Id(), updateOpts) + + _, err = pools.UpdateAssociateMember(networkingClient, d.Get("pool_id").(string), d.Id(), updateOpts).ExtractMember() + if err != nil { + return fmt.Errorf("Error updating OpenStack LBaaSV2 Member: %s", err) + } + + return resourceMemberV2Read(d, meta) +} + +func resourceMemberV2Delete(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: waitForMemberDelete(networkingClient, d.Get("pool_id").(string), 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 Member: %s", err) + } + + d.SetId("") + return nil +} + +func waitForMemberActive(networkingClient *gophercloud.ServiceClient, poolID string, memberID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + member, err := pools.GetAssociateMember(networkingClient, poolID, memberID).ExtractMember() + if err != nil { + return nil, "", err + } + + // The member resource has no Status attribute, so a successful Get is the best we can do + log.Printf("[DEBUG] OpenStack LBaaSV2 Member: %+v", member) + return member, "ACTIVE", nil + } +} + +func waitForMemberDelete(networkingClient *gophercloud.ServiceClient, poolID string, memberID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Member %s", memberID) + + member, err := pools.GetAssociateMember(networkingClient, poolID, memberID).ExtractMember() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return member, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Member %s", memberID) + return member, "DELETED", nil + } + } + + log.Printf("[DEBUG] Openstack LBaaSV2 Member: %+v", member) + err = pools.DeleteMember(networkingClient, poolID, memberID).ExtractErr() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return member, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Member %s", memberID) + return member, "DELETED", nil + } + } + + log.Printf("[DEBUG] OpenStack LBaaSV2 Member %s still active.", memberID) + return member, "ACTIVE", nil + } +} diff --git a/builtin/providers/openstack/resource_openstack_lb_member_v2_test.go b/builtin/providers/openstack/resource_openstack_lb_member_v2_test.go new file mode 100644 index 000000000000..4db4774a9e76 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_member_v2_test.go @@ -0,0 +1,171 @@ +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/pools" +) + +func TestAccLBV2Member_basic(t *testing.T) { + var member pools.Member + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLBV2MemberDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: TestAccLBV2MemberConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLBV2MemberExists(t, "openstack_lb_member_v2.member_1", &member), + ), + }, + resource.TestStep{ + Config: TestAccLBV2MemberConfig_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_lb_member_v2.member_1", "weight", "10"), + ), + }, + }, + }) +} + +func testAccCheckLBV2MemberDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckLBV2MemberDestroy) 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_lb_member_v2" { + continue + } + + log.Printf("[FINDME] rs.Primary.Attributes: %#v", rs.Primary.Attributes) + _, err := pools.GetAssociateMember(networkingClient, rs.Primary.Attributes["pool_id"], rs.Primary.ID).ExtractMember() + if err == nil { + return fmt.Errorf("Member still exists: %s", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckLBV2MemberExists(t *testing.T, n string, member *pools.Member) 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("(testAccCheckLBV2MemberExists) Error creating OpenStack networking client: %s", err) + } + + found, err := pools.GetAssociateMember(networkingClient, rs.Primary.Attributes["pool_id"], rs.Primary.ID).ExtractMember() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Member not found") + } + + *member = *found + + return nil + } +} + +var TestAccLBV2MemberConfig_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_lb_loadbalancer_v2" "loadbalancer_1" { + vip_subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + name = "tf_test_loadbalancer_v2" +} + +resource "openstack_lb_listener_v2" "listener_1" { + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = "${openstack_lb_loadbalancer_v2.loadbalancer_1.id}" + name = "tf_test_listener" +} + +resource "openstack_lb_pool_v2" "pool_1" { + protocol = "HTTP" + lb_method = "ROUND_ROBIN" + listener_id = "${openstack_lb_listener_v2.listener_1.id}" + name = "tf_test_pool" +} + +resource "openstack_lb_member_v2" "member_1" { + address = "192.168.199.10" + pool_id = "${openstack_lb_pool_v2.pool_1.id}" + protocol_port = 8080 + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" +}`) + +var TestAccLBV2MemberConfig_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_lb_loadbalancer_v2" "loadbalancer_1" { + vip_subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + name = "tf_test_loadbalancer_v2" +} + +resource "openstack_lb_listener_v2" "listener_1" { + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = "${openstack_lb_loadbalancer_v2.loadbalancer_1.id}" + name = "tf_test_listener" +} + +resource "openstack_lb_pool_v2" "pool_1" { + protocol = "HTTP" + lb_method = "ROUND_ROBIN" + listener_id = "${openstack_lb_listener_v2.listener_1.id}" + name = "tf_test_pool" +} + +resource "openstack_lb_member_v2" "member_1" { + address = "192.168.199.10" + pool_id = "${openstack_lb_pool_v2.pool_1.id}" + protocol_port = 8080 + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + weight = 10 + admin_state_up = "true" +}`) diff --git a/builtin/providers/openstack/resource_openstack_lb_monitor_v2.go b/builtin/providers/openstack/resource_openstack_lb_monitor_v2.go new file mode 100644 index 000000000000..edbb6f40d2c4 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_monitor_v2.go @@ -0,0 +1,286 @@ +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, + Update: resourceMonitorV2Update, + 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, + }, + + "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, + }, + "timeout": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "max_retries": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "url_path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "http_method": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "expected_codes": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "admin_state_up": &schema.Schema{ + Type: schema.TypeBool, + Optional: 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_lb_monitor_v2_test.go b/builtin/providers/openstack/resource_openstack_lb_monitor_v2_test.go new file mode 100644 index 000000000000..a6d1d18bd72a --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_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_lb_monitor_v2.monitor_1", &monitor), + ), + }, + resource.TestStep{ + Config: TestAccLBV2MonitorConfig_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_lb_monitor_v2.monitor_1", "name", "tf_test_monitor_updated"), + resource.TestCheckResourceAttr("openstack_lb_monitor_v2.monitor_1", "delay", "30"), + resource.TestCheckResourceAttr("openstack_lb_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_lb_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_lb_loadbalancer_v2" "loadbalancer_1" { + vip_subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + name = "tf_test_loadbalancer_v2" +} + +resource "openstack_lb_listener_v2" "listener_1" { + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = "${openstack_lb_loadbalancer_v2.loadbalancer_1.id}" + name = "tf_test_listener" +} + +resource "openstack_lb_pool_v2" "pool_1" { + protocol = "HTTP" + lb_method = "ROUND_ROBIN" + listener_id = "${openstack_lb_listener_v2.listener_1.id}" + name = "tf_test_pool" +} + +resource "openstack_lb_monitor_v2" "monitor_1" { + pool_id = "${openstack_lb_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_lb_loadbalancer_v2" "loadbalancer_1" { + vip_subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + name = "tf_test_loadbalancer_v2" +} + +resource "openstack_lb_listener_v2" "listener_1" { + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = "${openstack_lb_loadbalancer_v2.loadbalancer_1.id}" + name = "tf_test_listener" +} + +resource "openstack_lb_pool_v2" "pool_1" { + protocol = "HTTP" + lb_method = "ROUND_ROBIN" + listener_id = "${openstack_lb_listener_v2.listener_1.id}" + name = "tf_test_pool" +} + +resource "openstack_lb_monitor_v2" "monitor_1" { + pool_id = "${openstack_lb_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/builtin/providers/openstack/resource_openstack_lb_pool_v2.go b/builtin/providers/openstack/resource_openstack_lb_pool_v2.go new file mode 100644 index 000000000000..373d8b2eb6cd --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v2.go @@ -0,0 +1,319 @@ +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/pools" +) + +func resourcePoolV2() *schema.Resource { + return &schema.Resource{ + Create: resourcePoolV2Create, + Read: resourcePoolV2Read, + Update: resourcePoolV2Update, + Delete: resourcePoolV2Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "lTCP" && value != "HTTP" && value != "HTTPS" { + errors = append(errors, fmt.Errorf( + "Only 'TCP', 'HTTP', and 'HTTPS' are supported values for 'protocol'")) + } + return + }, + }, + + // One of loadbalancer_id or listener_id must be provided + "loadbalancer_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + // One of loadbalancer_id or listener_id must be provided + "listener_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "lb_method": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "ROUND_ROBIN" && value != "LEAST_CONNECTIONS" && value != "SOURCE_IP" { + errors = append(errors, fmt.Errorf( + "Only 'ROUND_ROBIN', 'LEAST_CONNECTIONS', and 'SOURCE_IP' are supported values for 'lb_method'")) + } + return + }, + }, + + "persistence": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "SOURCE_IP" && value != "HTTP_COOKIE" && value != "APP_COOKIE" { + errors = append(errors, fmt.Errorf( + "Only 'SOURCE_IP', 'HTTP_COOKIE', and 'APP_COOKIE' are supported values for 'persistence'")) + } + return + }, + }, + + "cookie_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + }, + + "admin_state_up": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + + "id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourcePoolV2Create(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) + var persistence pools.SessionPersistence + if p, ok := d.GetOk("persistence"); ok { + pV := (p.([]interface{}))[0].(map[string]interface{}) + + persistence = pools.SessionPersistence{ + Type: pV["type"].(string), + CookieName: pV["cookie_name"].(string), + } + } + createOpts := pools.CreateOpts{ + TenantID: d.Get("tenant_id").(string), + Name: d.Get("name").(string), + Description: d.Get("description").(string), + Protocol: pools.Protocol(d.Get("protocol").(string)), + LoadbalancerID: d.Get("loadbalancer_id").(string), + ListenerID: d.Get("listener_id").(string), + LBMethod: pools.LBMethod(d.Get("lb_method").(string)), + AdminStateUp: &adminStateUp, + } + // Must omit if not set + if persistence != (pools.SessionPersistence{}) { + createOpts.Persistence = &persistence + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + pool, err := pools.Create(networkingClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack LBaaSV2 pool: %s", err) + } + log.Printf("[INFO] pool ID: %s", pool.ID) + + log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 pool (%s) to become available.", pool.ID) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_CREATE"}, + Target: []string{"ACTIVE"}, + Refresh: waitForPoolActive(networkingClient, pool.ID), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return err + } + + d.SetId(pool.ID) + + return resourcePoolV2Read(d, meta) +} + +func resourcePoolV2Read(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) + } + + pool, err := pools.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "LBV2 Pool") + } + + log.Printf("[DEBUG] Retreived OpenStack LBaaSV2 Pool %s: %+v", d.Id(), pool) + + d.Set("lb_method", pool.LBMethod) + d.Set("protocol", pool.Protocol) + d.Set("description", pool.Description) + d.Set("tenant_id", pool.TenantID) + d.Set("admin_state_up", pool.AdminStateUp) + d.Set("name", pool.Name) + d.Set("id", pool.ID) + d.Set("persistence", pool.Persistence) + + return nil +} + +func resourcePoolV2Update(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 pools.UpdateOpts + if d.HasChange("lb_method") { + updateOpts.LBMethod = pools.LBMethod(d.Get("lb_method").(string)) + } + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("description") { + updateOpts.Description = d.Get("description").(string) + } + if d.HasChange("admin_state_up") { + asu := d.Get("admin_state_up").(bool) + updateOpts.AdminStateUp = &asu + } + + log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Pool %s with options: %+v", d.Id(), updateOpts) + + _, err = pools.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack LBaaSV2 Pool: %s", err) + } + + return resourcePoolV2Read(d, meta) +} + +func resourcePoolV2Delete(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: waitForPoolDelete(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 Pool: %s", err) + } + + d.SetId("") + return nil +} + +func waitForPoolActive(networkingClient *gophercloud.ServiceClient, poolID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + pool, err := pools.Get(networkingClient, poolID).Extract() + if err != nil { + return nil, "", err + } + + // The pool resource has no Status attribute, so a successful Get is the best we can do + log.Printf("[DEBUG] OpenStack LBaaSV2 Pool: %+v", pool) + return pool, "ACTIVE", nil + } +} + +func waitForPoolDelete(networkingClient *gophercloud.ServiceClient, poolID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Pool %s", poolID) + + pool, err := pools.Get(networkingClient, poolID).Extract() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return pool, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID) + return pool, "DELETED", nil + } + } + + log.Printf("[DEBUG] Openstack LBaaSV2 Pool: %+v", pool) + err = pools.Delete(networkingClient, poolID).ExtractErr() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return pool, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID) + return pool, "DELETED", nil + } + } + + log.Printf("[DEBUG] OpenStack LBaaSV2 Pool %s still active.", poolID) + return pool, "ACTIVE", nil + } +} diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v2_test.go b/builtin/providers/openstack/resource_openstack_lb_pool_v2_test.go new file mode 100644 index 000000000000..777600c1dbfb --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v2_test.go @@ -0,0 +1,155 @@ +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/pools" +) + +func TestAccLBV2Pool_basic(t *testing.T) { + var pool pools.Pool + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLBV2PoolDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: TestAccLBV2PoolConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLBV2PoolExists(t, "openstack_lb_pool_v2.pool_1", &pool), + ), + }, + resource.TestStep{ + Config: TestAccLBV2PoolConfig_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_lb_pool_v2.pool_1", "name", "tf_test_pool_update"), + ), + }, + }, + }) +} + +func testAccCheckLBV2PoolDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckLBV2PoolDestroy) 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_lb_pool_v2" { + continue + } + + _, err := pools.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Pool still exists: %s", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckLBV2PoolExists(t *testing.T, n string, pool *pools.Pool) 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("(testAccCheckLBV2PoolExists) Error creating OpenStack networking client: %s", err) + } + + found, err := pools.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Member not found") + } + + *pool = *found + + return nil + } +} + +var TestAccLBV2PoolConfig_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_lb_loadbalancer_v2" "loadbalancer_1" { + vip_subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + name = "tf_test_loadbalancer_v2" + } + + resource "openstack_lb_listener_v2" "listener_1" { + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = "${openstack_lb_loadbalancer_v2.loadbalancer_1.id}" + name = "tf_test_listener" + } + + resource "openstack_lb_pool_v2" "pool_1" { + protocol = "HTTP" + lb_method = "ROUND_ROBIN" + listener_id = "${openstack_lb_listener_v2.listener_1.id}" + name = "tf_test_pool" + }`) + +var TestAccLBV2PoolConfig_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_lb_loadbalancer_v2" "loadbalancer_1" { + vip_subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + name = "tf_test_loadbalancer_v2" + } + + resource "openstack_lb_listener_v2" "listener_1" { + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = "${openstack_lb_loadbalancer_v2.loadbalancer_1.id}" + name = "tf_test_listener" + } + + resource "openstack_lb_pool_v2" "pool_1" { + protocol = "HTTP" + lb_method = "LEAST_CONNECTIONS" + listener_id = "${openstack_lb_listener_v2.listener_1.id}" + name = "tf_test_pool_update" + admin_state_up = "true" + }`) diff --git a/vendor/vendor.json b/vendor/vendor.json index 9fab981a951e..7b823590793b 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1,6 +1,5 @@ { "comment": "", -<<<<<<< 8c3ff933951a5ab0111c0ef6b3553e42e4ce14af "ignore": "appengine test", "package": [ { diff --git a/website/source/docs/providers/openstack/r/lb_listener_v2.html.markdown b/website/source/docs/providers/openstack/r/lb_listener_v2.html.markdown new file mode 100644 index 000000000000..ac042223e5fa --- /dev/null +++ b/website/source/docs/providers/openstack/r/lb_listener_v2.html.markdown @@ -0,0 +1,78 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_lbaas_listener_v2" +sidebar_current: "docs-openstack-resource-lbaas-listener-v2" +description: |- + Manages a V2 listener resource within OpenStack. +--- + +# openstack\_lbaas\_listener\_v2 + +Manages a V2 listener resource within OpenStack. + +## Example Usage + +``` +resource "openstack_lbaas_listener_v2" "listener_1" { + protocol = "HTTP" + protocol_port = 8080 + loadbalancer_id = "d9415786-5f1a-428b-b35f-2f1523e146d2" +} +``` + +## 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 + Listener. + +* `protocol` = (Required) The protocol - can either be TCP, HTTP or HTTPS. + Changing this creates a new Listener. + +* `protocol_port` = (Required) The port on which to listen for client traffic. + Changing this creates a new Listener. + +* `tenant_id` - (Optional) Required for admins. The UUID of the tenant who owns + the Listener. Only administrative users can specify a tenant UUID + other than their own. Changing this creates a new Listener. + +* `loadbalancer_id` - (Required) The load balancer on which to provision this + Listener. Changing this creates a new Listener. + +* `name` - (Optional) Human-readable name for the Listener. Does not have + to be unique. + +* `default_pool_id` - (Optional) The ID of the default pool with which the + Listener is associated. Changing this creates a new Listener. + +* `description` - (Optional) Human-readable description for the Listener. + +* `connection_limit` - (Optional) The maximum number of connections allowed + for the Listener. + +* `default_tls_container_ref` - (Optional) A reference to a container of TLS + secrets. + +* `sni_container_refs` - (Optional) A list of references to TLS secrets. + +* `admin_state_up` - (Optional) The administrative state of the Listener. + A valid value is true (UP) or false (DOWN). + +## Attributes Reference + +The following attributes are exported: + +* `id` - The unique ID for the Listener. +* `protocol` - See Argument Reference above. +* `protocol_port` - See Argument Reference above. +* `tenant_id` - See Argument Reference above. +* `name` - See Argument Reference above. +* `default_port_id` - See Argument Reference above. +* `description` - See Argument Reference above. +* `connection_limit` - See Argument Reference above. +* `default_tls_container_ref` - See Argument Reference above. +* `sni_container_refs` - See Argument Reference above. +* `admin_state_up` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/lb_loadbalancer_v2.html.markdown b/website/source/docs/providers/openstack/r/lb_loadbalancer_v2.html.markdown new file mode 100644 index 000000000000..4e04dda9fcca --- /dev/null +++ b/website/source/docs/providers/openstack/r/lb_loadbalancer_v2.html.markdown @@ -0,0 +1,68 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_lb_loadbalancer_v2" +sidebar_current: "docs-openstack-resource-lb-loadbalancer-v2" +description: |- + Manages a V2 loadbalancer resource within OpenStack. +--- + +# openstack\_lb\_loadbalancer\_v2 + +Manages a V2 loadbalancer resource within OpenStack. + +## Example Usage + +``` +resource "openstack_lbaas_loadbalancer_v2" "lb_1" { + vip_subnet_id = "d9415786-5f1a-428b-b35f-2f1523e146d2" +} +``` + +## 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 LB member. If omitted, the + `OS_REGION_NAME` environment variable is used. Changing this creates a new + LB member. + +* `vip_subnet_id` - (Required) The network on which to allocate the + Loadbalancer's address. A tenant can only create Loadbalancers on networks + authorized by policy (e.g. networks that belong to them or networks that + are shared). Changing this creates a new loadbalancer. + +* `name` - (Optional) Human-readable name for the Loadbalancer. Does not have + to be unique. + +* `description` - (Optional) Human-readable description for the Loadbalancer. + +* `tenant_id` - (Optional) Required for admins. The UUID of the tenant who owns + the Loadbalancer. Only administrative users can specify a tenant UUID + other than their own. Changing this creates a new loadbalancer. + +* `vip_address` - (Optional) The ip address of the load balancer. + Changing this creates a new loadbalancer. + +* `admin_state_up` - (Optional) The administrative state of the Loadbalancer. + A valid value is true (UP) or false (DOWN). + +* `flavor` - (Optional) The UUID of a flavor. Changing this creates a new + loadbalancer. + +* `provider` - (Optional) The name of the provider. Changing this creates a new + loadbalancer. + +## Attributes Reference + +The following attributes are exported: + +* `region` - See Argument Reference above. +* `vip_subnet_id` - See Argument Reference above. +* `name` - See Argument Reference above. +* `description` - See Argument Reference above. +* `tenant_id` - See Argument Reference above. +* `vip_address` - See Argument Reference above. +* `admin_state_up` - See Argument Reference above. +* `flavor` - See Argument Reference above. +* `provider` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/lb_member_v2.html.markdown b/website/source/docs/providers/openstack/r/lb_member_v2.html.markdown new file mode 100644 index 000000000000..214ef7a2231d --- /dev/null +++ b/website/source/docs/providers/openstack/r/lb_member_v2.html.markdown @@ -0,0 +1,63 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_lbaas_member_v2" +sidebar_current: "docs-openstack-resource-lbaas-member-v2" +description: |- + Manages a V2 member resource within OpenStack. +--- + +# openstack\_lbaas\_member\_v2 + +Manages a V2 member resource within OpenStack. + +## Example Usage + +``` +resource "openstack_lbaas_member_v2" "member_1" { + address = "192.168.199.23" + protocol_port = 8080 +} +``` + +## 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 + member. + +* `name` - (Optional) Human-readable name for the member. + +* `tenant_id` - (Optional) Required for admins. The UUID of the tenant who owns + the member. Only administrative users can specify a tenant UUID + other than their own. Changing this creates a new member. + +* `address` - (Required) The IP address of the member to receive traffic from + the load balancer. Changing this creates a new member. + +* `protocol_port` - (Required) The port on which to listen for client traffic. + Changing this creates a new member. + +* `weight` - (Optional) A positive integer value that indicates the relative + portion of traffic that this member should receive from the pool. For + example, a member with a weight of 10 receives five times as much traffic + as a member with a weight of 2. + +* `admin_state_up` - (Optional) The administrative state of the member. + A valid value is true (UP) or false (DOWN). + +## Attributes Reference + +The following attributes are exported: + +* `id` - The unique ID for the member. +* `name` - See Argument Reference above. +* `weight` - See Argument Reference above. +* `admin_state_up` - See Argument Reference above. +* `tenant_id` - See Argument Reference above. +* `subnet_id` - See Argument Reference above. +* `pool_id` - See Argument Reference above. +* `address` - See Argument Reference above. +* `protocol_port` - See Argument Reference above. 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. diff --git a/website/source/docs/providers/openstack/r/lb_pool_v2.html.markdown b/website/source/docs/providers/openstack/r/lb_pool_v2.html.markdown new file mode 100644 index 000000000000..f779c0f0d3ce --- /dev/null +++ b/website/source/docs/providers/openstack/r/lb_pool_v2.html.markdown @@ -0,0 +1,86 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_lbaas_pool_v2" +sidebar_current: "docs-openstack-resource-lbaas-pool-v2" +description: |- + Manages a V2 pool resource within OpenStack. +--- + +# openstack\_lbaas\_pool\_v2 + +Manages a V2 pool resource within OpenStack. + +## Example Usage + +``` +resource "openstack_lbaas_pool_v2" "pool_1" { + protocol = "ProtocolHTTP" + lb_method = "LBMethodRoundRobin" + listener_id = "d9415786-5f1a-428b-b35f-2f1523e146d2" + persistence { + type = "HTTP_COOKIE" + cookie_name = "testCookie" + } +} +``` + +## 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 + pool. + +* `tenant_id` - (Optional) Required for admins. The UUID of the tenant who owns + the pool. Only administrative users can specify a tenant UUID + other than their own. Changing this creates a new pool. + +* `name` - (Optional) Human-readable name for the pool. + +* `description` - (Optional) Human-readable description for the pool. + +* `protocol` = (Required) The protocol - can either be TCP, HTTP or HTTPS. + Changing this creates a new pool. + +* `loadbalancer_id` - (Optional) The load balancer on which to provision this + pool. Changing this creates a new pool. + Note: One of LoadbalancerID or ListenerID must be provided. + +* `listener_id` - (Optional) The Listener on which the members of the pool + will be associated with. Changing this creates a new pool. + Note: One of LoadbalancerID or ListenerID must be provided. + +* `lb_method` - (Required) The algorithm used to distribute load between the + members of the pool. The current specification supports + LBMethodRoundRobin, LBMethodLeastConnections and LBMethodSourceIp as valid + values for this attribute. + +* `persistence` - Omit this field to prevent session persistence. Indicates + whether connections in the same session will be processed by the same Pool + member or not. Changing this creates a new pool. + +* `admin_state_up` - (Optional) The administrative state of the pool. + A valid value is true (UP) or false (DOWN). + +The `persistence` argument supports: + +* `type` - (Required) The type of persistence mode. The current specification + supports SOURCE_IP, HTTP_COOKIE, and APP_COOKIE. + +* `cookie_name` - (Required) The name of the cookie if persistence mode is set + appropriately. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The unique ID for the pool. +* `tenant_id` - See Argument Reference above. +* `name` - See Argument Reference above. +* `description` - See Argument Reference above. +* `protocol` - See Argument Reference above. +* `lb_method` - See Argument Reference above. +* `persistence` - See Argument Reference above. +* `admin_state_up` - See Argument Reference above. diff --git a/website/source/layouts/openstack.erb b/website/source/layouts/openstack.erb index 985ba40ba766..5f1b43b59b1c 100644 --- a/website/source/layouts/openstack.erb +++ b/website/source/layouts/openstack.erb @@ -91,6 +91,21 @@ > openstack_lb_vip_v1 + > + openstack_lb_loadbalancer_v2 + + > + openstack_lbaas_listener_v2 + + > + openstack_lbaas_pool_v2 + + > + openstack_lbaas_member_v2 + + > + openstack_lbaas_monitor_v2 +