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/github.com/rackspace/gophercloud/openstack/client.go b/vendor/github.com/rackspace/gophercloud/openstack/client.go index b533e834e50c..0585211c75df 100644 --- a/vendor/github.com/rackspace/gophercloud/openstack/client.go +++ b/vendor/github.com/rackspace/gophercloud/openstack/client.go @@ -283,25 +283,12 @@ func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.Endpoi // NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 block storage service. func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("volume") + eo.ApplyDefaults("volumev2") url, err := client.EndpointLocator(eo) if err != nil { return nil, err } - - // Force using v2 API - if strings.Contains(url, "/v1") { - url = strings.Replace(url, "/v1", "/v2", -1) - } - if !strings.Contains(url, "/v2") { - return nil, fmt.Errorf("Block Storage v2 endpoint not found") - } - - return &gophercloud.ServiceClient{ - ProviderClient: client, - Endpoint: url, - ResourceBase: url, - }, nil + return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil } // NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1 diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/doc.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/doc.go new file mode 100644 index 000000000000..247a75facbee --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/doc.go @@ -0,0 +1,5 @@ +// Package lbaas_v2 provides information and interaction with the Load Balancer +// as a Service v2 extension for the OpenStack Networking service. +// lbaas v2 api docs: http://developer.openstack.org/api-ref-networking-v2-ext.html#lbaas-v2.0 +// lbaas v2 api schema: https://github.com/openstack/neutron-lbaas/blob/master/neutron_lbaas/extensions/loadbalancerv2.py +package lbaas_v2 diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/fixtures.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/fixtures.go new file mode 100644 index 000000000000..4c22f63d31d6 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/fixtures.go @@ -0,0 +1,214 @@ +// +build fixtures + +package listeners + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + "github.com/rackspace/gophercloud/testhelper/client" +) + +// ListenersListBody contains the canned body of a listeners list response. +const ListenersListBody = ` +{ + "listeners":[ + { + "id": "db902c0c-d5ff-4753-b465-668ad9656918", + "tenant_id": "310df60f-2a10-4ee5-9554-98393092194c", + "name": "web", + "description": "listener config for the web tier", + "loadbalancers": [{"id": "53306cda-815d-4354-9444-59e09da9c3c5"}], + "protocol": "HTTP", + "protocol_port": 80, + "default_pool_id": "fad389a3-9a4a-4762-a365-8c7038508b5d", + "admin_state_up": true, + "default_tls_container_ref": "2c433435-20de-4411-84ae-9cc8917def76", + "sni_container_refs": ["3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"] + }, + { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "tenant_id": "310df60f-2a10-4ee5-9554-98393092194c", + "name": "db", + "description": "listener config for the db tier", + "loadbalancers": [{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "protocol": "TCP", + "protocol_port": 3306, + "default_pool_id": "41efe233-7591-43c5-9cf7-923964759f9e", + "connection_limit": 2000, + "admin_state_up": true, + "default_tls_container_ref": "2c433435-20de-4411-84ae-9cc8917def76", + "sni_container_refs": ["3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"] + } + ] +} +` + +// SingleServerBody is the canned body of a Get request on an existing listener. +const SingleListenerBody = ` +{ + "listener": { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "tenant_id": "310df60f-2a10-4ee5-9554-98393092194c", + "name": "db", + "description": "listener config for the db tier", + "loadbalancers": [{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "protocol": "TCP", + "protocol_port": 3306, + "default_pool_id": "41efe233-7591-43c5-9cf7-923964759f9e", + "connection_limit": 2000, + "admin_state_up": true, + "default_tls_container_ref": "2c433435-20de-4411-84ae-9cc8917def76", + "sni_container_refs": ["3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"] + } +} +` + +// PostUpdateListenerBody is the canned response body of a Update request on an existing listener. +const PostUpdateListenerBody = ` +{ + "listener": { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "tenant_id": "310df60f-2a10-4ee5-9554-98393092194c", + "name": "NewListenerName", + "description": "listener config for the db tier", + "loadbalancers": [{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "protocol": "TCP", + "protocol_port": 3306, + "default_pool_id": "41efe233-7591-43c5-9cf7-923964759f9e", + "connection_limit": 1000, + "admin_state_up": true, + "default_tls_container_ref": "2c433435-20de-4411-84ae-9cc8917def76", + "sni_container_refs": ["3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"] + } +} +` + +var ( + ListenerWeb = Listener{ + ID: "db902c0c-d5ff-4753-b465-668ad9656918", + TenantID: "310df60f-2a10-4ee5-9554-98393092194c", + Name: "web", + Description: "listener config for the web tier", + Loadbalancers: []LoadBalancerID{{ID: "53306cda-815d-4354-9444-59e09da9c3c5"}}, + Protocol: "HTTP", + ProtocolPort: 80, + DefaultPoolID: "fad389a3-9a4a-4762-a365-8c7038508b5d", + AdminStateUp: true, + DefaultTlsContainerRef: "2c433435-20de-4411-84ae-9cc8917def76", + SniContainerRefs: []string{"3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"}, + } + ListenerDb = Listener{ + ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + TenantID: "310df60f-2a10-4ee5-9554-98393092194c", + Name: "db", + Description: "listener config for the db tier", + Loadbalancers: []LoadBalancerID{{ID: "79e05663-7f03-45d2-a092-8b94062f22ab"}}, + Protocol: "TCP", + ProtocolPort: 3306, + DefaultPoolID: "41efe233-7591-43c5-9cf7-923964759f9e", + ConnLimit: 2000, + AdminStateUp: true, + DefaultTlsContainerRef: "2c433435-20de-4411-84ae-9cc8917def76", + SniContainerRefs: []string{"3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"}, + } + ListenerUpdated = Listener{ + ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + TenantID: "310df60f-2a10-4ee5-9554-98393092194c", + Name: "NewListenerName", + Description: "listener config for the db tier", + Loadbalancers: []LoadBalancerID{{ID: "79e05663-7f03-45d2-a092-8b94062f22ab"}}, + Protocol: "TCP", + ProtocolPort: 3306, + DefaultPoolID: "41efe233-7591-43c5-9cf7-923964759f9e", + ConnLimit: 1000, + AdminStateUp: true, + DefaultTlsContainerRef: "2c433435-20de-4411-84ae-9cc8917def76", + SniContainerRefs: []string{"3d328d82-2547-4921-ac2f-61c3b452b5ff", "b3cfd7e3-8c19-455c-8ebb-d78dfd8f7e7d"}, + } +) + +// HandleListenerListSuccessfully sets up the test server to respond to a listener List request. +func HandleListenerListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/listeners", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, ListenersListBody) + case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": + fmt.Fprintf(w, `{ "listeners": [] }`) + default: + t.Fatalf("/v2.0/lbaas/listeners invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleListenerCreationSuccessfully sets up the test server to respond to a listener creation request +// with a given response. +func HandleListenerCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/listeners", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "listener": { + "loadbalancer_id": "79e05663-7f03-45d2-a092-8b94062f22ab", + "protocol": "TCP", + "name": "db", + "admin_state_up": true, + "default_tls_container_ref": "2c433435-20de-4411-84ae-9cc8917def76", + "default_pool_id": "41efe233-7591-43c5-9cf7-923964759f9e", + "protocol_port": 3306 + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandleListenerGetSuccessfully sets up the test server to respond to a listener Get request. +func HandleListenerGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleListenerBody) + }) +} + +// HandleListenerDeletionSuccessfully sets up the test server to respond to a listener deletion request. +func HandleListenerDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleListenerUpdateSuccessfully sets up the test server to respond to a listener Update request. +func HandleListenerUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "listener": { + "name": "NewListenerName", + "connection_limit": 1001 + } + }`) + + fmt.Fprintf(w, PostUpdateListenerBody) + }) +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go new file mode 100644 index 000000000000..a62e0cd9e803 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go @@ -0,0 +1,279 @@ +package listeners + +import ( + "fmt" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// AdminState gives users a solid type to work with for create and update +// operations. It is recommended that users use the `Up` and `Down` enums. +type AdminState *bool + +type listenerOpts struct { + // Required. The protocol - can either be TCP, HTTP or HTTPS. + Protocol Protocol + + // Required. The port on which to listen for client traffic. + ProtocolPort int + + // Required for admins. Indicates the owner of the Listener. + TenantID string + + // Required. The load balancer on which to provision this listener. + LoadbalancerID string + + // Human-readable name for the Listener. Does not have to be unique. + Name string + + // Optional. The ID of the default pool with which the Listener is associated. + DefaultPoolID string + + // Optional. Human-readable description for the Listener. + Description string + + // Optional. The maximum number of connections allowed for the Listener. + ConnLimit *int + + // Optional. A reference to a container of TLS secrets. + DefaultTlsContainerRef string + + // Optional. A list of references to TLS secrets. + SniContainerRefs []string + + // Optional. The administrative state of the Listener. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool +} + +// Convenience vars for AdminStateUp values. +var ( + iTrue = true + iFalse = false + + Up AdminState = &iTrue + Down AdminState = &iFalse +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToListenerListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the floating IP attributes you want to see returned. SortKey allows you to +// sort by a particular listener attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + ID string `q:"id"` + Name string `q:"name"` + AdminStateUp *bool `q:"admin_state_up"` + TenantID string `q:"tenant_id"` + LoadbalancerID string `q:"loadbalancer_id"` + DefaultPoolID string `q:"default_pool_id"` + Protocol string `q:"protocol"` + ProtocolPort int `q:"protocol_port"` + ConnectionLimit int `q:"connection_limit"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToListenerListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToListenerListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// List returns a Pager which allows you to iterate over a collection of +// routers. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those routers that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToListenerListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return ListenerPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +type Protocol string + +// Supported attributes for create/update operations. +const ( + ProtocolTCP Protocol = "TCP" + ProtocolHTTP Protocol = "HTTP" + ProtocolHTTPS Protocol = "HTTPS" +) + +var ( + errLoadbalancerIdRequired = fmt.Errorf("LoadbalancerID is required") + errProtocolRequired = fmt.Errorf("Protocol is required") + errProtocolPortRequired = fmt.Errorf("ProtocolPort is required") +) + +// CreateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Create operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type CreateOptsBuilder interface { + ToListenerCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts listenerOpts + +// ToListenerCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToListenerCreateMap() (map[string]interface{}, error) { + l := make(map[string]interface{}) + + if opts.LoadbalancerID != "" { + l["loadbalancer_id"] = opts.LoadbalancerID + } else { + return nil, errLoadbalancerIdRequired + } + if opts.Protocol != "" { + l["protocol"] = opts.Protocol + } else { + return nil, errProtocolRequired + } + if opts.ProtocolPort != 0 { + l["protocol_port"] = opts.ProtocolPort + } else { + return nil, errProtocolPortRequired + } + if opts.AdminStateUp != nil { + l["admin_state_up"] = &opts.AdminStateUp + } + if opts.Name != "" { + l["name"] = opts.Name + } + if opts.TenantID != "" { + l["tenant_id"] = opts.TenantID + } + if opts.DefaultPoolID != "" { + l["default_pool_id"] = opts.DefaultPoolID + } + if opts.Description != "" { + l["description"] = opts.Description + } + if opts.ConnLimit != nil { + l["connection_limit"] = &opts.ConnLimit + } + if opts.DefaultTlsContainerRef != "" { + l["default_tls_container_ref"] = opts.DefaultTlsContainerRef + } + if opts.SniContainerRefs != nil { + l["sni_container_refs"] = opts.SniContainerRefs + } + + return map[string]interface{}{"listener": l}, nil +} + +// Create is an operation which provisions a new Listeners based on the +// configuration defined in the CreateOpts struct. Once the request is +// validated and progress has started on the provisioning process, a +// CreateResult will be returned. +// +// Users with an admin role can create Listeners on behalf of other tenants by +// specifying a TenantID attribute different than their own. +func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { + var res CreateResult + + reqBody, err := opts.ToListenerCreateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil) + return res +} + +// Get retrieves a particular Listeners based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Get(resourceURL(c, id), &res.Body, nil) + return res +} + +// UpdateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Update operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type UpdateOptsBuilder interface { + ToListenerUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is the common options struct used in this package's Update +// operation. +type UpdateOpts listenerOpts + +// ToListenerUpdateMap casts a UpdateOpts struct to a map. +func (opts UpdateOpts) ToListenerUpdateMap() (map[string]interface{}, error) { + l := make(map[string]interface{}) + + if opts.Name != "" { + l["name"] = opts.Name + } + if opts.Description != "" { + l["description"] = opts.Description + } + if opts.ConnLimit != nil { + l["connection_limit"] = &opts.ConnLimit + } + if opts.DefaultTlsContainerRef != "" { + l["default_tls_container_ref"] = opts.DefaultTlsContainerRef + } + if opts.SniContainerRefs != nil { + l["sni_container_refs"] = opts.SniContainerRefs + } + if opts.AdminStateUp != nil { + l["admin_state_up"] = &opts.AdminStateUp + } + + return map[string]interface{}{"listener": l}, nil +} + +// Update is an operation which modifies the attributes of the specified Listener. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult { + var res UpdateResult + + reqBody, err := opts.ToListenerUpdateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + + return res +} + +// Delete will permanently delete a particular Listeners based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { + var res DeleteResult + _, res.Err = c.Delete(resourceURL(c, id), nil) + return res +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go new file mode 100644 index 000000000000..77a084a58aa1 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go @@ -0,0 +1,139 @@ +package listeners + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" + "github.com/rackspace/gophercloud/pagination" +) + +type LoadBalancerID struct { + ID string `mapstructure:"id" json:"id"` +} + +// Listener is the primary load balancing configuration object that specifies +// the loadbalancer and port on which client traffic is received, as well +// as other details such as the load balancing method to be use, protocol, etc. +type Listener struct { + // The unique ID for the Listener. + ID string `mapstructure:"id" json:"id"` + + // Owner of the Listener. Only an admin user can specify a tenant ID other than its own. + TenantID string `mapstructure:"tenant_id" json:"tenant_id"` + + // Human-readable name for the Listener. Does not have to be unique. + Name string `mapstructure:"name" json:"name"` + + // Human-readable description for the Listener. + Description string `mapstructure:"description" json:"description"` + + // The protocol to loadbalance. A valid value is TCP, HTTP, or HTTPS. + Protocol string `mapstructure:"protocol" json:"protocol"` + + // The port on which to listen to client traffic that is associated with the + // Loadbalancer. A valid value is from 0 to 65535. + ProtocolPort int `mapstructure:"protocol_port" json:"protocol_port"` + + // The UUID of default pool. Must have compatible protocol with listener. + DefaultPoolID string `mapstructure:"default_pool_id" json:"default_pool_id"` + + // A list of load balancer IDs. + Loadbalancers []LoadBalancerID `mapstructure:"loadbalancers" json:"loadbalancers"` + + // The maximum number of connections allowed for the Loadbalancer. Default is -1, + // meaning no limit. + ConnLimit int `mapstructure:"connection_limit" json:"connection_limit"` + + // The list of references to TLS secrets. + SniContainerRefs []string `mapstructure:"sni_container_refs" json:"sni_container_refs"` + + // Optional. A reference to a container of TLS secrets. + DefaultTlsContainerRef string `mapstructure:"default_tls_container_ref" json:"default_tls_container_ref"` + + // The administrative state of the Listener. A valid value is true (UP) or false (DOWN). + AdminStateUp bool `mapstructure:"admin_state_up" json:"admin_state_up"` + + Pools []pools.Pool `mapstructure:"pools" json:"pools"` +} + +// ListenerPage is the page returned by a pager when traversing over a +// collection of routers. +type ListenerPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of routers has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (p ListenerPage) NextPageURL() (string, error) { + type resp struct { + Links []gophercloud.Link `mapstructure:"listeners_links"` + } + + var r resp + err := mapstructure.Decode(p.Body, &r) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(r.Links) +} + +// IsEmpty checks whether a RouterPage struct is empty. +func (p ListenerPage) IsEmpty() (bool, error) { + is, err := ExtractListeners(p) + if err != nil { + return true, nil + } + return len(is) == 0, nil +} + +// ExtractListeners accepts a Page struct, specifically a ListenerPage struct, +// and extracts the elements into a slice of Listener structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractListeners(page pagination.Page) ([]Listener, error) { + var resp struct { + Listeners []Listener `mapstructure:"listeners" json:"listeners"` + } + err := mapstructure.Decode(page.(ListenerPage).Body, &resp) + return resp.Listeners, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a router. +func (r commonResult) Extract() (*Listener, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Listener *Listener `mapstructure:"listener" json:"listener"` + } + + err := mapstructure.Decode(r.Body, &res) + + return res.Listener, err +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go new file mode 100644 index 000000000000..b4cb90b08b3a --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go @@ -0,0 +1,16 @@ +package listeners + +import "github.com/rackspace/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "listeners" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/fixtures.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/fixtures.go new file mode 100644 index 000000000000..cb431587623f --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/fixtures.go @@ -0,0 +1,278 @@ +// +build fixtures + +package loadbalancers + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + "github.com/rackspace/gophercloud/testhelper/client" + + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" +) + +// LoadbalancersListBody contains the canned body of a loadbalancer list response. +const LoadbalancersListBody = ` +{ + "loadbalancers":[ + { + "id": "c331058c-6a40-4144-948e-b9fb1df9db4b", + "tenant_id": "54030507-44f7-473c-9342-b4d14a95f692", + "name": "web_lb", + "description": "lb config for the web tier", + "vip_subnet_id": "8a49c438-848f-467b-9655-ea1548708154", + "vip_address": "10.30.176.47", + "flavor": "small", + "provider": "haproxy", + "admin_state_up": true, + "provisioning_status": "ACTIVE", + "operating_status": "ONLINE" + }, + { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "tenant_id": "54030507-44f7-473c-9342-b4d14a95f692", + "name": "db_lb", + "description": "lb config for the db tier", + "vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + "vip_address": "10.30.176.48", + "flavor": "medium", + "provider": "haproxy", + "admin_state_up": true, + "provisioning_status": "PENDING_CREATE", + "operating_status": "OFFLINE" + } + ] +} +` + +// SingleLoadbalancerBody is the canned body of a Get request on an existing loadbalancer. +const SingleLoadbalancerBody = ` +{ + "loadbalancer": { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "tenant_id": "54030507-44f7-473c-9342-b4d14a95f692", + "name": "db_lb", + "description": "lb config for the db tier", + "vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + "vip_address": "10.30.176.48", + "flavor": "medium", + "provider": "haproxy", + "admin_state_up": true, + "provisioning_status": "PENDING_CREATE", + "operating_status": "OFFLINE" + } +} +` + +// PostUpdateLoadbalancerBody is the canned response body of a Update request on an existing loadbalancer. +const PostUpdateLoadbalancerBody = ` +{ + "loadbalancer": { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "tenant_id": "54030507-44f7-473c-9342-b4d14a95f692", + "name": "NewLoadbalancerName", + "description": "lb config for the db tier", + "vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + "vip_address": "10.30.176.48", + "flavor": "medium", + "provider": "haproxy", + "admin_state_up": true, + "provisioning_status": "PENDING_CREATE", + "operating_status": "OFFLINE" + } +} +` + +// SingleLoadbalancerBody is the canned body of a Get request on an existing loadbalancer. +const LoadbalancerStatuesesTree = ` +{ + "statuses" : { + "loadbalancer": { + "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + "name": "db_lb", + "provisioning_status": "PENDING_UPDATE", + "operating_status": "ACTIVE", + "listeners": [{ + "id": "db902c0c-d5ff-4753-b465-668ad9656918", + "name": "db", + "pools": [{ + "id": "fad389a3-9a4a-4762-a365-8c7038508b5d", + "name": "db", + "healthmonitor": { + "id": "67306cda-815d-4354-9fe4-59e09da9c3c5", + "type":"PING" + }, + "members":[{ + "id": "2a280670-c202-4b0b-a562-34077415aabf", + "name": "db", + "address": "10.0.2.11", + "protocol_port": 80 + }] + }] + }] + } + } +} +` + +var ( + LoadbalancerWeb = LoadBalancer{ + ID: "c331058c-6a40-4144-948e-b9fb1df9db4b", + TenantID: "54030507-44f7-473c-9342-b4d14a95f692", + Name: "web_lb", + Description: "lb config for the web tier", + VipSubnetID: "8a49c438-848f-467b-9655-ea1548708154", + VipAddress: "10.30.176.47", + Flavor: "small", + Provider: "haproxy", + AdminStateUp: true, + ProvisioningStatus: "ACTIVE", + OperatingStatus: "ONLINE", + } + LoadbalancerDb = LoadBalancer{ + ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + TenantID: "54030507-44f7-473c-9342-b4d14a95f692", + Name: "db_lb", + Description: "lb config for the db tier", + VipSubnetID: "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + VipAddress: "10.30.176.48", + Flavor: "medium", + Provider: "haproxy", + AdminStateUp: true, + ProvisioningStatus: "PENDING_CREATE", + OperatingStatus: "OFFLINE", + } + LoadbalancerUpdated = LoadBalancer{ + ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + TenantID: "54030507-44f7-473c-9342-b4d14a95f692", + Name: "NewLoadbalancerName", + Description: "lb config for the db tier", + VipSubnetID: "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + VipAddress: "10.30.176.48", + Flavor: "medium", + Provider: "haproxy", + AdminStateUp: true, + ProvisioningStatus: "PENDING_CREATE", + OperatingStatus: "OFFLINE", + } + LoadbalancerStatusesTree = LoadBalancer{ + ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", + Name: "db_lb", + ProvisioningStatus: "PENDING_UPDATE", + OperatingStatus: "ACTIVE", + Listeners: []listeners.Listener{{ + ID: "db902c0c-d5ff-4753-b465-668ad9656918", + Name: "db", + Pools: []pools.Pool{{ + ID: "fad389a3-9a4a-4762-a365-8c7038508b5d", + Name: "db", + Monitor: monitors.Monitor{ + ID: "67306cda-815d-4354-9fe4-59e09da9c3c5", + Type: "PING", + }, + Members: []pools.Member{{ + ID: "2a280670-c202-4b0b-a562-34077415aabf", + Name: "db", + Address: "10.0.2.11", + ProtocolPort: 80, + }}, + }}, + }}, + } +) + +// HandleLoadbalancerListSuccessfully sets up the test server to respond to a loadbalancer List request. +func HandleLoadbalancerListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, LoadbalancersListBody) + case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": + fmt.Fprintf(w, `{ "loadbalancers": [] }`) + default: + t.Fatalf("/v2.0/lbaas/loadbalancers invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleLoadbalancerCreationSuccessfully sets up the test server to respond to a loadbalancer creation request +// with a given response. +func HandleLoadbalancerCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "loadbalancer": { + "name": "db_lb", + "vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + "vip_address": "10.30.176.48", + "flavor": "medium", + "provider": "haproxy", + "admin_state_up": true + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandleLoadbalancerGetSuccessfully sets up the test server to respond to a loadbalancer Get request. +func HandleLoadbalancerGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleLoadbalancerBody) + }) +} + +// HandleLoadbalancerGetStatusesTree sets up the test server to respond to a loadbalancer Get statuses tree request. +func HandleLoadbalancerGetStatusesTree(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/statuses", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, LoadbalancerStatuesesTree) + }) +} + +// HandleLoadbalancerDeletionSuccessfully sets up the test server to respond to a loadbalancer deletion request. +func HandleLoadbalancerDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleLoadbalancerUpdateSuccessfully sets up the test server to respond to a loadbalancer Update request. +func HandleLoadbalancerUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "loadbalancer": { + "name": "NewLoadbalancerName" + } + }`) + + fmt.Fprintf(w, PostUpdateLoadbalancerBody) + }) +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go new file mode 100644 index 000000000000..6607b8a6b86b --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go @@ -0,0 +1,248 @@ +package loadbalancers + +import ( + "fmt" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// AdminState gives users a solid type to work with for create and update +// operations. It is recommended that users use the `Up` and `Down` enums. +type AdminState *bool + +type loadbalancerOpts struct { + // Optional. Human-readable name for the Loadbalancer. Does not have to be unique. + Name string + + // Optional. Human-readable description for the Loadbalancer. + Description string + + // 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). + VipSubnetID string + + // Required for admins. The UUID of the tenant who owns the Loadbalancer. + // Only administrative users can specify a tenant UUID other than their own. + TenantID string + + // Optional. The IP address of the Loadbalancer. + VipAddress string + + // Optional. The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool + + // Optional. The UUID of a flavor. + Flavor string + + // Optional. The name of the provider. + Provider string +} + +// Convenience vars for AdminStateUp values. +var ( + iTrue = true + iFalse = false + + Up AdminState = &iTrue + Down AdminState = &iFalse +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToLoadbalancerListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the Loadbalancer attributes you want to see returned. SortKey allows you to +// sort by a particular attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + Description string `q:"description"` + AdminStateUp *bool `q:"admin_state_up"` + TenantID string `q:"tenant_id"` + ProvisioningStatus string `q:"provisioning_status"` + VipAddress string `q:"vip_address"` + VipSubnetID string `q:"vip_subnet_id"` + ID string `q:"id"` + OperatingStatus string `q:"operating_status"` + Name string `q:"name"` + Flavor string `q:"flavor"` + Provider string `q:"provider"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToLoadbalancerListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToLoadbalancerListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// List returns a Pager which allows you to iterate over a collection of +// routers. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those routers that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToLoadbalancerListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return LoadbalancerPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +var ( + errVipSubnetIDRequried = fmt.Errorf("VipSubnetID is required") +) + +// CreateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Create operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type CreateOptsBuilder interface { + ToLoadbalancerCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts loadbalancerOpts + +// ToLoadbalancerCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToLoadbalancerCreateMap() (map[string]interface{}, error) { + l := make(map[string]interface{}) + + if opts.VipSubnetID != "" { + l["vip_subnet_id"] = opts.VipSubnetID + } else { + return nil, errVipSubnetIDRequried + } + if opts.AdminStateUp != nil { + l["admin_state_up"] = &opts.AdminStateUp + } + if opts.Name != "" { + l["name"] = opts.Name + } + if opts.TenantID != "" { + l["tenant_id"] = opts.TenantID + } + if opts.Description != "" { + l["description"] = opts.Description + } + if opts.VipAddress != "" { + l["vip_address"] = opts.VipAddress + } + if opts.Flavor != "" { + l["flavor"] = opts.Flavor + } + if opts.Provider != "" { + l["provider"] = opts.Provider + } + + return map[string]interface{}{"loadbalancer": l}, nil +} + +// Create is an operation which provisions a new loadbalancer based on the +// configuration defined in the CreateOpts struct. Once the request is +// validated and progress has started on the provisioning process, a +// CreateResult will be returned. +// +// Users with an admin role can create loadbalancers on behalf of other tenants by +// specifying a TenantID attribute different than their own. +func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { + var res CreateResult + + reqBody, err := opts.ToLoadbalancerCreateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil) + return res +} + +// Get retrieves a particular Loadbalancer based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Get(resourceURL(c, id), &res.Body, nil) + return res +} + +// UpdateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Update operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type UpdateOptsBuilder interface { + ToLoadbalancerUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is the common options struct used in this package's Update +// operation. +type UpdateOpts loadbalancerOpts + +// ToLoadbalancerUpdateMap casts a UpdateOpts struct to a map. +func (opts UpdateOpts) ToLoadbalancerUpdateMap() (map[string]interface{}, error) { + l := make(map[string]interface{}) + + if opts.Name != "" { + l["name"] = opts.Name + } + if opts.Description != "" { + l["description"] = opts.Description + } + if opts.AdminStateUp != nil { + l["admin_state_up"] = &opts.AdminStateUp + } + + return map[string]interface{}{"loadbalancer": l}, nil +} + +// Update is an operation which modifies the attributes of the specified Loadbalancer. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult { + var res UpdateResult + + reqBody, err := opts.ToLoadbalancerUpdateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + + return res +} + +// Delete will permanently delete a particular Loadbalancer based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { + var res DeleteResult + _, res.Err = c.Delete(resourceURL(c, id), nil) + return res +} + +func GetStatuses(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Get(statusRootURL(c, id), &res.Body, nil) + return res +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go new file mode 100644 index 000000000000..911c15639b66 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go @@ -0,0 +1,146 @@ +package loadbalancers + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" + "github.com/rackspace/gophercloud/pagination" +) + +// LoadBalancer is the primary load balancing configuration object that specifies +// the virtual IP address on which client traffic is received, as well +// as other details such as the load balancing method to be use, protocol, etc. +type LoadBalancer struct { + // Human-readable description for the Loadbalancer. + Description string `mapstructure:"description" json:"description"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) or false (DOWN). + AdminStateUp bool `mapstructure:"admin_state_up" json:"admin_state_up"` + + // Owner of the LoadBalancer. Only an admin user can specify a tenant ID other than its own. + TenantID string `mapstructure:"tenant_id" json:"tenant_id"` + + // The provisioning status of the LoadBalancer. This value is ACTIVE, PENDING_CREATE or ERROR. + ProvisioningStatus string `mapstructure:"provisioning_status" json:"provisioning_status"` + + // The IP address of the Loadbalancer. + VipAddress string `mapstructure:"vip_address" json:"vip_address"` + + // The UUID of the subnet on which to allocate the virtual IP for the Loadbalancer address. + VipSubnetID string `mapstructure:"vip_subnet_id" json:"vip_subnet_id"` + + // The unique ID for the LoadBalancer. + ID string `mapstructure:"id" json:"id"` + + // The operating status of the LoadBalancer. This value is ONLINE or OFFLINE. + OperatingStatus string `mapstructure:"operating_status" json:"operating_status"` + + // Human-readable name for the LoadBalancer. Does not have to be unique. + Name string `mapstructure:"name" json:"name"` + + // The UUID of a flavor if set. + Flavor string `mapstructure:"flavor" json:"flavor"` + + // The name of the provider. + Provider string `mapstructure:"provider" json:"provider"` + + Listeners []listeners.Listener `mapstructure:"listeners" json:"listeners"` +} + +type StatusTree struct { + Loadbalancer *LoadBalancer `mapstructure:"loadbalancer" json:"loadbalancer"` +} + +// LoadbalancerPage is the page returned by a pager when traversing over a +// collection of routers. +type LoadbalancerPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of routers has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (p LoadbalancerPage) NextPageURL() (string, error) { + type resp struct { + Links []gophercloud.Link `mapstructure:"loadbalancers_links"` + } + + var r resp + err := mapstructure.Decode(p.Body, &r) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(r.Links) +} + +// IsEmpty checks whether a RouterPage struct is empty. +func (p LoadbalancerPage) IsEmpty() (bool, error) { + is, err := ExtractLoadbalancers(p) + if err != nil { + return true, nil + } + return len(is) == 0, nil +} + +// ExtractLoadbalancers accepts a Page struct, specifically a LoadbalancerPage struct, +// and extracts the elements into a slice of LoadBalancer structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractLoadbalancers(page pagination.Page) ([]LoadBalancer, error) { + var resp struct { + LoadBalancers []LoadBalancer `mapstructure:"loadbalancers" json:"loadbalancers"` + } + err := mapstructure.Decode(page.(LoadbalancerPage).Body, &resp) + + return resp.LoadBalancers, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a router. +func (r commonResult) Extract() (*LoadBalancer, error) { + if r.Err != nil { + return nil, r.Err + } + var res struct { + LoadBalancer *LoadBalancer `mapstructure:"loadbalancer" json:"loadbalancer"` + } + err := mapstructure.Decode(r.Body, &res) + + return res.LoadBalancer, err +} + +// Extract is a function that accepts a result and extracts a Loadbalancer. +func (r commonResult) ExtractStatuses() (*StatusTree, error) { + if r.Err != nil { + return nil, r.Err + } + var res struct { + LoadBalancer *StatusTree `mapstructure:"statuses" json:"statuses"` + } + err := mapstructure.Decode(r.Body, &res) + + return res.LoadBalancer, err +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go new file mode 100644 index 000000000000..a307490581b7 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go @@ -0,0 +1,21 @@ +package loadbalancers + +import "github.com/rackspace/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "loadbalancers" + statusPath = "statuses" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} + +func statusRootURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id, statusPath) +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/fixtures.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/fixtures.go new file mode 100644 index 000000000000..ad0b4883a4fa --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/fixtures.go @@ -0,0 +1,216 @@ +// +build fixtures + +package monitors + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + "github.com/rackspace/gophercloud/testhelper/client" +) + +// HealthmonitorsListBody contains the canned body of a healthmonitor list response. +const HealthmonitorsListBody = ` +{ + "healthmonitors":[ + { + "admin_state_up":true, + "tenant_id":"83657cfcdfe44cd5920adaf26c48ceea", + "delay":10, + "name":"web", + "max_retries":1, + "timeout":1, + "type":"PING", + "pools": [{"id": "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d"}], + "id":"466c8345-28d8-4f84-a246-e04380b0461d" + }, + { + "admin_state_up":true, + "tenant_id":"83657cfcdfe44cd5920adaf26c48ceea", + "delay":5, + "name":"db", + "expected_codes":"200", + "max_retries":2, + "http_method":"GET", + "timeout":2, + "url_path":"/", + "type":"HTTP", + "pools": [{"id": "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}], + "id":"5d4b5228-33b0-4e60-b225-9b727c1a20e7" + } + ] +} +` + +// SingleHealthmonitorBody is the canned body of a Get request on an existing healthmonitor. +const SingleHealthmonitorBody = ` +{ + "healthmonitor": { + "admin_state_up":true, + "tenant_id":"83657cfcdfe44cd5920adaf26c48ceea", + "delay":5, + "name":"db", + "expected_codes":"200", + "max_retries":2, + "http_method":"GET", + "timeout":2, + "url_path":"/", + "type":"HTTP", + "pools": [{"id": "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}], + "id":"5d4b5228-33b0-4e60-b225-9b727c1a20e7" + } +} +` + +// PostUpdateHealthmonitorBody is the canned response body of a Update request on an existing healthmonitor. +const PostUpdateHealthmonitorBody = ` +{ + "healthmonitor": { + "admin_state_up":true, + "tenant_id":"83657cfcdfe44cd5920adaf26c48ceea", + "delay":3, + "name":"NewHealthmonitorName", + "expected_codes":"301", + "max_retries":10, + "http_method":"GET", + "timeout":20, + "url_path":"/another_check", + "type":"HTTP", + "pools": [{"id": "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}], + "id":"5d4b5228-33b0-4e60-b225-9b727c1a20e7" + } +} +` + +var ( + HealthmonitorWeb = Monitor{ + AdminStateUp: true, + Name: "web", + TenantID: "83657cfcdfe44cd5920adaf26c48ceea", + Delay: 10, + MaxRetries: 1, + Timeout: 1, + Type: "PING", + ID: "466c8345-28d8-4f84-a246-e04380b0461d", + Pools: []PoolID{{ID: "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d"}}, + } + HealthmonitorDb = Monitor{ + AdminStateUp: true, + Name: "db", + TenantID: "83657cfcdfe44cd5920adaf26c48ceea", + Delay: 5, + ExpectedCodes: "200", + MaxRetries: 2, + Timeout: 2, + URLPath: "/", + Type: "HTTP", + HTTPMethod: "GET", + ID: "5d4b5228-33b0-4e60-b225-9b727c1a20e7", + Pools: []PoolID{{ID: "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}}, + } + HealthmonitorUpdated = Monitor{ + AdminStateUp: true, + Name: "NewHealthmonitorName", + TenantID: "83657cfcdfe44cd5920adaf26c48ceea", + Delay: 3, + ExpectedCodes: "301", + MaxRetries: 10, + Timeout: 20, + URLPath: "/another_check", + Type: "HTTP", + HTTPMethod: "GET", + ID: "5d4b5228-33b0-4e60-b225-9b727c1a20e7", + Pools: []PoolID{{ID: "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}}, + } +) + +// HandleHealthmonitorListSuccessfully sets up the test server to respond to a healthmonitor List request. +func HandleHealthmonitorListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, HealthmonitorsListBody) + case "556c8345-28d8-4f84-a246-e04380b0461d": + fmt.Fprintf(w, `{ "healthmonitors": [] }`) + default: + t.Fatalf("/v2.0/lbaas/healthmonitors invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleHealthmonitorCreationSuccessfully sets up the test server to respond to a healthmonitor creation request +// with a given response. +func HandleHealthmonitorCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "healthmonitor": { + "type":"HTTP", + "pool_id":"84f1b61f-58c4-45bf-a8a9-2dafb9e5214d", + "tenant_id":"453105b9-1754-413f-aab1-55f1af620750", + "delay":20, + "name":"db", + "timeout":10, + "max_retries":5, + "url_path":"/check", + "expected_codes":"200-299" + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandleHealthmonitorGetSuccessfully sets up the test server to respond to a healthmonitor Get request. +func HandleHealthmonitorGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors/5d4b5228-33b0-4e60-b225-9b727c1a20e7", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleHealthmonitorBody) + }) +} + +// HandleHealthmonitorDeletionSuccessfully sets up the test server to respond to a healthmonitor deletion request. +func HandleHealthmonitorDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors/5d4b5228-33b0-4e60-b225-9b727c1a20e7", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleHealthmonitorUpdateSuccessfully sets up the test server to respond to a healthmonitor Update request. +func HandleHealthmonitorUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors/5d4b5228-33b0-4e60-b225-9b727c1a20e7", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "healthmonitor": { + "name": "NewHealthmonitorName", + "delay": 3, + "timeout": 20, + "max_retries": 10, + "url_path": "/another_check", + "expected_codes": "301" + } + }`) + + fmt.Fprintf(w, PostUpdateHealthmonitorBody) + }) +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go new file mode 100644 index 000000000000..92e7e83f31d1 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go @@ -0,0 +1,304 @@ +package monitors + +import ( + "fmt" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +type monitorOpts struct { + // Required. The Pool to Monitor. + PoolID string + + // Optional. The Name of the Monitor. + Name string + + // Required for admins. Indicates the owner of the Loadbalancer. + TenantID string + + // Required. The type of probe, which is PING, TCP, HTTP, or HTTPS, that is + // sent by the load balancer to verify the member state. + Type string + + // Required. The time, in seconds, between sending probes to members. + Delay int + + // 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. + Timeout int + + // Required. Number of permissible ping failures before changing the member's + // status to INACTIVE. Must be a number between 1 and 10. + MaxRetries int + + // Required for HTTP(S) types. URI path that will be accessed if Monitor type + // is HTTP or HTTPS. + URLPath string + + // Required for HTTP(S) types. The HTTP method used for requests by the + // Monitor. If this attribute is not specified, it defaults to "GET". + HTTPMethod string + + // 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". + ExpectedCodes string + + AdminStateUp *bool +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToMonitorListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the Monitor attributes you want to see returned. SortKey allows you to +// sort by a particular Monitor attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + ID string `q:"id"` + Name string `q:"name"` + TenantID string `q:"tenant_id"` + PoolID string `q:"pool_id"` + Type string `q:"type"` + Delay int `q:"delay"` + Timeout int `q:"timeout"` + MaxRetries int `q:"max_retries"` + HTTPMethod string `q:"http_method"` + URLPath string `q:"url_path"` + ExpectedCodes string `q:"expected_codes"` + AdminStateUp *bool `q:"admin_state_up"` + Status string `q:"status"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToMonitorListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToMonitorListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// List returns a Pager which allows you to iterate over a collection of +// health monitors. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those health monitors that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToMonitorListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return MonitorPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Constants that represent approved monitoring types. +const ( + TypePING = "PING" + TypeTCP = "TCP" + TypeHTTP = "HTTP" + TypeHTTPS = "HTTPS" +) + +var ( + errPoolIDRequired = fmt.Errorf("PoolID to monitor is required") + errValidTypeRequired = fmt.Errorf("A valid Type is required. Supported values are PING, TCP, HTTP and HTTPS") + errDelayRequired = fmt.Errorf("Delay is required") + errTimeoutRequired = fmt.Errorf("Timeout is required") + errMaxRetriesRequired = fmt.Errorf("MaxRetries is required") + errURLPathRequired = fmt.Errorf("URL path is required") + errExpectedCodesRequired = fmt.Errorf("ExpectedCodes is required") + errDelayMustGETimeout = fmt.Errorf("Delay must be greater than or equal to timeout") +) + +// CreateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Create operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type CreateOptsBuilder interface { + ToMonitorCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts monitorOpts + +// ToMonitorCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToMonitorCreateMap() (map[string]interface{}, error) { + l := make(map[string]interface{}) + allowed := map[string]bool{TypeHTTP: true, TypeHTTPS: true, TypeTCP: true, TypePING: true} + + if allowed[opts.Type] { + l["type"] = opts.Type + } else { + return nil, errValidTypeRequired + } + if opts.Type == TypeHTTP || opts.Type == TypeHTTPS { + if opts.URLPath != "" { + l["url_path"] = opts.URLPath + } else { + return nil, errURLPathRequired + } + if opts.ExpectedCodes != "" { + l["expected_codes"] = opts.ExpectedCodes + } else { + return nil, errExpectedCodesRequired + } + } + if opts.PoolID != "" { + l["pool_id"] = opts.PoolID + } else { + return nil, errPoolIDRequired + } + if opts.Delay != 0 { + l["delay"] = opts.Delay + } else { + return nil, errDelayRequired + } + if opts.Timeout != 0 { + l["timeout"] = opts.Timeout + } else { + return nil, errMaxRetriesRequired + } + if opts.MaxRetries != 0 { + l["max_retries"] = opts.MaxRetries + } else { + return nil, errMaxRetriesRequired + } + if opts.AdminStateUp != nil { + l["admin_state_up"] = &opts.AdminStateUp + } + if opts.Name != "" { + l["name"] = opts.Name + } + if opts.TenantID != "" { + l["tenant_id"] = opts.TenantID + } + if opts.HTTPMethod != "" { + l["http_method"] = opts.HTTPMethod + } + + return map[string]interface{}{"healthmonitor": l}, nil +} + +/* + Create is an operation which provisions a new Health Monitor. There are + different types of Monitor you can provision: PING, TCP or HTTP(S). Below + are examples of how to create each one. + + Here is an example config struct to use when creating a PING or TCP Monitor: + + CreateOpts{Type: TypePING, Delay: 20, Timeout: 10, MaxRetries: 3} + CreateOpts{Type: TypeTCP, Delay: 20, Timeout: 10, MaxRetries: 3} + + Here is an example config struct to use when creating a HTTP(S) Monitor: + + CreateOpts{Type: TypeHTTP, Delay: 20, Timeout: 10, MaxRetries: 3, + HttpMethod: "HEAD", ExpectedCodes: "200", PoolID: "2c946bfc-1804-43ab-a2ff-58f6a762b505"} +*/ + +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { + var res CreateResult + + reqBody, err := opts.ToMonitorCreateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil) + return res +} + +// Get retrieves a particular Health Monitor based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Get(resourceURL(c, id), &res.Body, nil) + return res +} + +// UpdateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Update operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type UpdateOptsBuilder interface { + ToMonitorUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is the common options struct used in this package's Update +// operation. +type UpdateOpts monitorOpts + +// ToMonitorUpdateMap casts a UpdateOpts struct to a map. +func (opts UpdateOpts) ToMonitorUpdateMap() (map[string]interface{}, error) { + l := make(map[string]interface{}) + + if opts.URLPath != "" { + l["url_path"] = opts.URLPath + } + if opts.ExpectedCodes != "" { + l["expected_codes"] = opts.ExpectedCodes + } + if opts.Delay != 0 { + l["delay"] = opts.Delay + } + if opts.Timeout != 0 { + l["timeout"] = opts.Timeout + } + if opts.MaxRetries != 0 { + l["max_retries"] = opts.MaxRetries + } + if opts.AdminStateUp != nil { + l["admin_state_up"] = &opts.AdminStateUp + } + if opts.Name != "" { + l["name"] = opts.Name + } + if opts.HTTPMethod != "" { + l["http_method"] = opts.HTTPMethod + } + + return map[string]interface{}{"healthmonitor": l}, nil +} + +// Update is an operation which modifies the attributes of the specified Monitor. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult { + var res UpdateResult + + reqBody, err := opts.ToMonitorUpdateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + + return res +} + +// Delete will permanently delete a particular Monitor based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { + var res DeleteResult + _, res.Err = c.Delete(resourceURL(c, id), nil) + return res +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go new file mode 100644 index 000000000000..da363c02b074 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go @@ -0,0 +1,160 @@ +package monitors + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +type PoolID struct { + ID string `mapstructure:"id" json:"id"` +} + +// Monitor represents a load balancer health monitor. A health monitor is used +// to determine whether or not back-end members of the VIP's pool are usable +// for processing a request. A pool can have several health monitors associated +// with it. There are different types of health monitors supported: +// +// PING: used to ping the members using ICMP. +// TCP: used to connect to the members using TCP. +// HTTP: used to send an HTTP request to the member. +// HTTPS: used to send a secure HTTP request to the member. +// +// When a pool has several monitors associated with it, each member of the pool +// is monitored by all these monitors. If any monitor declares the member as +// unhealthy, then the member status is changed to INACTIVE and the member +// won't participate in its pool's load balancing. In other words, ALL monitors +// must declare the member to be healthy for it to stay ACTIVE. +type Monitor struct { + // The unique ID for the Monitor. + ID string + + // The Name of the Monitor. + Name string + + // Only an administrative user can specify a tenant ID + // other than its own. + TenantID string `json:"tenant_id" mapstructure:"tenant_id"` + + // The type of probe sent by the load balancer to verify the member state, + // which is PING, TCP, HTTP, or HTTPS. + Type string + + // The time, in seconds, between sending probes to members. + Delay int + + // The maximum number of seconds for a monitor to wait for a connection to be + // established before it times out. This value must be less than the delay value. + Timeout int + + // Number of allowed connection failures before changing the status of the + // member to INACTIVE. A valid value is from 1 to 10. + MaxRetries int `json:"max_retries" mapstructure:"max_retries"` + + // The HTTP method that the monitor uses for requests. + HTTPMethod string `json:"http_method" mapstructure:"http_method"` + + // The HTTP path of the request sent by the monitor to test the health of a + // member. Must be a string beginning with a forward slash (/). + URLPath string `json:"url_path" mapstructure:"url_path"` + + // Expected HTTP codes for a passing HTTP(S) monitor. + ExpectedCodes string `json:"expected_codes" mapstructure:"expected_codes"` + + // The administrative state of the health monitor, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"` + + // The status of the health monitor. Indicates whether the health monitor is + // operational. + Status string + + // List of pools that are associated with the health monitor. + Pools []PoolID `mapstructure:"pools" json:"pools"` +} + +type Pool struct { +} + +// MonitorPage is the page returned by a pager when traversing over a +// collection of health monitors. +type MonitorPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of monitors has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (p MonitorPage) NextPageURL() (string, error) { + type resp struct { + Links []gophercloud.Link `mapstructure:"healthmonitors_links"` + } + + var r resp + err := mapstructure.Decode(p.Body, &r) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(r.Links) +} + +// IsEmpty checks whether a PoolPage struct is empty. +func (p MonitorPage) IsEmpty() (bool, error) { + is, err := ExtractMonitors(p) + if err != nil { + return true, nil + } + return len(is) == 0, nil +} + +// ExtractMonitors accepts a Page struct, specifically a MonitorPage struct, +// and extracts the elements into a slice of Monitor structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractMonitors(page pagination.Page) ([]Monitor, error) { + var resp struct { + Monitors []Monitor `mapstructure:"healthmonitors" json:"healthmonitors"` + } + + err := mapstructure.Decode(page.(MonitorPage).Body, &resp) + + return resp.Monitors, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a monitor. +func (r commonResult) Extract() (*Monitor, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Monitor *Monitor `json:"healthmonitor" mapstructure:"healthmonitor"` + } + + err := mapstructure.Decode(r.Body, &res) + + return res.Monitor, err +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go new file mode 100644 index 000000000000..a3abba428a3b --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go @@ -0,0 +1,16 @@ +package monitors + +import "github.com/rackspace/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "healthmonitors" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/fixtures.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/fixtures.go new file mode 100644 index 000000000000..8e6edc5fff04 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/fixtures.go @@ -0,0 +1,389 @@ +// +build fixtures + +package pools + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + "github.com/rackspace/gophercloud/testhelper/client" +) + +// PoolsListBody contains the canned body of a pool list response. +const PoolsListBody = ` +{ + "pools":[ + { + "lb_algorithm":"ROUND_ROBIN", + "protocol":"HTTP", + "description":"", + "healthmonitor_id": "466c8345-28d8-4f84-a246-e04380b0461d", + "members":[{"id": "53306cda-815d-4354-9fe4-59e09da9c3c5"}], + "listeners":[{"id": "2a280670-c202-4b0b-a562-34077415aabf"}], + "loadbalancers":[{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "id":"72741b06-df4d-4715-b142-276b6bce75ab", + "name":"web", + "admin_state_up":true, + "tenant_id":"83657cfcdfe44cd5920adaf26c48ceea", + "provider": "haproxy" + }, + { + "lb_algorithm":"LEAST_CONNECTION", + "protocol":"HTTP", + "description":"", + "healthmonitor_id": "5f6c8345-28d8-4f84-a246-e04380b0461d", + "members":[{"id": "67306cda-815d-4354-9fe4-59e09da9c3c5"}], + "listeners":[{"id": "2a280670-c202-4b0b-a562-34077415aabf"}], + "loadbalancers":[{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "id":"c3741b06-df4d-4715-b142-276b6bce75ab", + "name":"db", + "admin_state_up":true, + "tenant_id":"83657cfcdfe44cd5920adaf26c48ceea", + "provider": "haproxy" + } + ] +} +` + +// SinglePoolBody is the canned body of a Get request on an existing pool. +const SinglePoolBody = ` +{ + "pool": { + "lb_algorithm":"LEAST_CONNECTION", + "protocol":"HTTP", + "description":"", + "healthmonitor_id": "5f6c8345-28d8-4f84-a246-e04380b0461d", + "members":[{"id": "67306cda-815d-4354-9fe4-59e09da9c3c5"}], + "listeners":[{"id": "2a280670-c202-4b0b-a562-34077415aabf"}], + "loadbalancers":[{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "id":"c3741b06-df4d-4715-b142-276b6bce75ab", + "name":"db", + "admin_state_up":true, + "tenant_id":"83657cfcdfe44cd5920adaf26c48ceea", + "provider": "haproxy" + } +} +` + +// PostUpdatePoolBody is the canned response body of a Update request on an existing pool. +const PostUpdatePoolBody = ` +{ + "pool": { + "lb_algorithm":"LEAST_CONNECTION", + "protocol":"HTTP", + "description":"", + "healthmonitor_id": "5f6c8345-28d8-4f84-a246-e04380b0461d", + "members":[{"id": "67306cda-815d-4354-9fe4-59e09da9c3c5"}], + "listeners":[{"id": "2a280670-c202-4b0b-a562-34077415aabf"}], + "loadbalancers":[{"id": "79e05663-7f03-45d2-a092-8b94062f22ab"}], + "id":"c3741b06-df4d-4715-b142-276b6bce75ab", + "name":"db", + "admin_state_up":true, + "tenant_id":"83657cfcdfe44cd5920adaf26c48ceea", + "provider": "haproxy" + } +} +` + +var ( + PoolWeb = Pool{ + LBMethod: "ROUND_ROBIN", + Protocol: "HTTP", + Description: "", + MonitorID: "466c8345-28d8-4f84-a246-e04380b0461d", + TenantID: "83657cfcdfe44cd5920adaf26c48ceea", + AdminStateUp: true, + Name: "web", + Members: []Member{{ID: "53306cda-815d-4354-9fe4-59e09da9c3c5"}}, + ID: "72741b06-df4d-4715-b142-276b6bce75ab", + Loadbalancers: []LoadBalancerID{{ID: "79e05663-7f03-45d2-a092-8b94062f22ab"}}, + Listeners: []ListenerID{{ID: "2a280670-c202-4b0b-a562-34077415aabf"}}, + Provider: "haproxy", + } + PoolDb = Pool{ + LBMethod: "LEAST_CONNECTION", + Protocol: "HTTP", + Description: "", + MonitorID: "5f6c8345-28d8-4f84-a246-e04380b0461d", + TenantID: "83657cfcdfe44cd5920adaf26c48ceea", + AdminStateUp: true, + Name: "db", + Members: []Member{{ID: "67306cda-815d-4354-9fe4-59e09da9c3c5"}}, + ID: "c3741b06-df4d-4715-b142-276b6bce75ab", + Loadbalancers: []LoadBalancerID{{ID: "79e05663-7f03-45d2-a092-8b94062f22ab"}}, + Listeners: []ListenerID{{ID: "2a280670-c202-4b0b-a562-34077415aabf"}}, + Provider: "haproxy", + } + PoolUpdated = Pool{ + LBMethod: "LEAST_CONNECTION", + Protocol: "HTTP", + Description: "", + MonitorID: "5f6c8345-28d8-4f84-a246-e04380b0461d", + TenantID: "83657cfcdfe44cd5920adaf26c48ceea", + AdminStateUp: true, + Name: "db", + Members: []Member{{ID: "67306cda-815d-4354-9fe4-59e09da9c3c5"}}, + ID: "c3741b06-df4d-4715-b142-276b6bce75ab", + Loadbalancers: []LoadBalancerID{{ID: "79e05663-7f03-45d2-a092-8b94062f22ab"}}, + Listeners: []ListenerID{{ID: "2a280670-c202-4b0b-a562-34077415aabf"}}, + Provider: "haproxy", + } +) + +// HandlePoolListSuccessfully sets up the test server to respond to a pool List request. +func HandlePoolListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, PoolsListBody) + case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": + fmt.Fprintf(w, `{ "pools": [] }`) + default: + t.Fatalf("/v2.0/lbaas/pools invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandlePoolCreationSuccessfully sets up the test server to respond to a pool creation request +// with a given response. +func HandlePoolCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/pools", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "pool": { + "lb_algorithm": "ROUND_ROBIN", + "protocol": "HTTP", + "name": "Example pool", + "tenant_id": "2ffc6e22aae24e4795f87155d24c896f", + "loadbalancer_id": "79e05663-7f03-45d2-a092-8b94062f22ab" + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandlePoolGetSuccessfully sets up the test server to respond to a pool Get request. +func HandlePoolGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/c3741b06-df4d-4715-b142-276b6bce75ab", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SinglePoolBody) + }) +} + +// HandlePoolDeletionSuccessfully sets up the test server to respond to a pool deletion request. +func HandlePoolDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/c3741b06-df4d-4715-b142-276b6bce75ab", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandlePoolUpdateSuccessfully sets up the test server to respond to a pool Update request. +func HandlePoolUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/c3741b06-df4d-4715-b142-276b6bce75ab", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "pool": { + "name": "NewPoolName", + "lb_algorithm": "LEAST_CONNECTIONS" + } + }`) + + fmt.Fprintf(w, PostUpdatePoolBody) + }) +} + +// MembersListBody contains the canned body of a member list response. +const MembersListBody = ` +{ + "members":[ + { + "id": "2a280670-c202-4b0b-a562-34077415aabf", + "address": "10.0.2.10", + "weight": 5, + "name": "web", + "subnet_id": "1981f108-3c48-48d2-b908-30f7d28532c9", + "tenant_id": "2ffc6e22aae24e4795f87155d24c896f", + "admin_state_up":true, + "protocol_port": 80 + }, + { + "id": "fad389a3-9a4a-4762-a365-8c7038508b5d", + "address": "10.0.2.11", + "weight": 10, + "name": "db", + "subnet_id": "1981f108-3c48-48d2-b908-30f7d28532c9", + "tenant_id": "2ffc6e22aae24e4795f87155d24c896f", + "admin_state_up":false, + "protocol_port": 80 + } + ] +} +` + +// SingleMemberBody is the canned body of a Get request on an existing member. +const SingleMemberBody = ` +{ + "member": { + "id": "fad389a3-9a4a-4762-a365-8c7038508b5d", + "address": "10.0.2.11", + "weight": 10, + "name": "db", + "subnet_id": "1981f108-3c48-48d2-b908-30f7d28532c9", + "tenant_id": "2ffc6e22aae24e4795f87155d24c896f", + "admin_state_up":false, + "protocol_port": 80 + } +} +` + +// PostUpdateMemberBody is the canned response body of a Update request on an existing member. +const PostUpdateMemberBody = ` +{ + "member": { + "id": "fad389a3-9a4a-4762-a365-8c7038508b5d", + "address": "10.0.2.11", + "weight": 10, + "name": "db", + "subnet_id": "1981f108-3c48-48d2-b908-30f7d28532c9", + "tenant_id": "2ffc6e22aae24e4795f87155d24c896f", + "admin_state_up":false, + "protocol_port": 80 + } +} +` + +var ( + MemberWeb = Member{ + SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", + TenantID: "2ffc6e22aae24e4795f87155d24c896f", + AdminStateUp: true, + Name: "web", + ID: "2a280670-c202-4b0b-a562-34077415aabf", + Address: "10.0.2.10", + Weight: 5, + ProtocolPort: 80, + } + MemberDb = Member{ + SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", + TenantID: "2ffc6e22aae24e4795f87155d24c896f", + AdminStateUp: false, + Name: "db", + ID: "fad389a3-9a4a-4762-a365-8c7038508b5d", + Address: "10.0.2.11", + Weight: 10, + ProtocolPort: 80, + } + MemberUpdated = Member{ + SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", + TenantID: "2ffc6e22aae24e4795f87155d24c896f", + AdminStateUp: false, + Name: "db", + ID: "fad389a3-9a4a-4762-a365-8c7038508b5d", + Address: "10.0.2.11", + Weight: 10, + ProtocolPort: 80, + } +) + +// HandleMemberListSuccessfully sets up the test server to respond to a member List request. +func HandleMemberListSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + r.ParseForm() + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprintf(w, MembersListBody) + case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": + fmt.Fprintf(w, `{ "members": [] }`) + default: + t.Fatalf("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandleMemberCreationSuccessfully sets up the test server to respond to a member creation request +// with a given response. +func HandleMemberCreationSuccessfully(t *testing.T, response string) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "member": { + "address": "10.0.2.11", + "weight": 10, + "name": "db", + "subnet_id": "1981f108-3c48-48d2-b908-30f7d28532c9", + "tenant_id": "2ffc6e22aae24e4795f87155d24c896f", + "protocol_port": 80 + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, response) + }) +} + +// HandleMemberGetSuccessfully sets up the test server to respond to a member Get request. +func HandleMemberGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members/2a280670-c202-4b0b-a562-34077415aabf", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprintf(w, SingleMemberBody) + }) +} + +// HandleMemberDeletionSuccessfully sets up the test server to respond to a member deletion request. +func HandleMemberDeletionSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members/2a280670-c202-4b0b-a562-34077415aabf", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleMemberUpdateSuccessfully sets up the test server to respond to a member Update request. +func HandleMemberUpdateSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members/2a280670-c202-4b0b-a562-34077415aabf", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "member": { + "name": "newMemberName", + "weight": 4 + } + }`) + + fmt.Fprintf(w, PostUpdateMemberBody) + }) +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go new file mode 100644 index 000000000000..706ce96161ef --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go @@ -0,0 +1,485 @@ +package pools + +import ( + "fmt" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// AdminState gives users a solid type to work with for create and update +// operations. It is recommended that users use the `Up` and `Down` enums. +type AdminState *bool + +type poolOpts struct { + // Only required if the caller has an admin role and wants to create a pool + // for another tenant. + TenantID string + + // Optional. Name of the pool. + Name string + + // Optional. Human-readable description for the pool. + Description string + + // Required. The protocol used by the pool members, you can use either + // ProtocolTCP, ProtocolHTTP, or ProtocolHTTPS. + Protocol Protocol + + // The Loadbalancer on which the members of the pool will be associated with. + // Note: one of LoadbalancerID or ListenerID must be provided. + LoadbalancerID string + + // The Listener on which the members of the pool will be associated with. + // Note: one of LoadbalancerID or ListenerID must be provided. + ListenerID string + + // 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. + LBMethod LBMethod + + // Optional. Omit this field to prevent session persistence. + Persistence *SessionPersistence + + // Optional. The administrative state of the Pool. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool +} + +// Convenience vars for AdminStateUp values. +var ( + iTrue = true + iFalse = false + + Up AdminState = &iTrue + Down AdminState = &iFalse +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToPoolListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the Pool attributes you want to see returned. SortKey allows you to +// sort by a particular Pool attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + LBMethod string `q:"lb_algorithm"` + Protocol string `q:"protocol"` + TenantID string `q:"tenant_id"` + AdminStateUp *bool `q:"admin_state_up"` + Name string `q:"name"` + ID string `q:"id"` + LoadbalancerID string `q:"loadbalancer_id"` + ListenerID string `q:"listener_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToPoolListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToPoolListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// List returns a Pager which allows you to iterate over a collection of +// pools. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those pools that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToPoolListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return PoolPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +type LBMethod string +type Protocol string + +// Supported attributes for create/update operations. +const ( + LBMethodRoundRobin LBMethod = "ROUND_ROBIN" + LBMethodLeastConnections LBMethod = "LEAST_CONNECTIONS" + LBMethodSourceIp LBMethod = "SOURCE_IP" + + ProtocolTCP Protocol = "TCP" + ProtocolHTTP Protocol = "HTTP" + ProtocolHTTPS Protocol = "HTTPS" +) + +var ( + errLoadbalancerOrListenerRequired = fmt.Errorf("A ListenerID or LoadbalancerID is required") + errValidLBMethodRequired = fmt.Errorf("A valid LBMethod is required. Supported values are ROUND_ROBIN, LEAST_CONNECTIONS, SOURCE_IP") + errValidProtocolRequired = fmt.Errorf("A valid Protocol is required. Supported values are TCP, HTTP, HTTPS") +) + +// CreateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Create operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type CreateOptsBuilder interface { + ToPoolCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts poolOpts + +// ToPoolCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToPoolCreateMap() (map[string]interface{}, error) { + l := make(map[string]interface{}) + allowedLBMethod := map[LBMethod]bool{LBMethodRoundRobin: true, LBMethodLeastConnections: true, LBMethodSourceIp: true} + allowedProtocol := map[Protocol]bool{ProtocolTCP: true, ProtocolHTTP: true, ProtocolHTTPS: true} + + if allowedLBMethod[opts.LBMethod] { + l["lb_algorithm"] = opts.LBMethod + } else { + return nil, errValidLBMethodRequired + } + if allowedProtocol[opts.Protocol] { + l["protocol"] = opts.Protocol + } else { + return nil, errValidProtocolRequired + } + if opts.LoadbalancerID == "" && opts.ListenerID == "" { + return nil, errLoadbalancerOrListenerRequired + } else { + if opts.LoadbalancerID != "" { + l["loadbalancer_id"] = opts.LoadbalancerID + } + if opts.ListenerID != "" { + l["listener_id"] = opts.ListenerID + } + } + if opts.AdminStateUp != nil { + l["admin_state_up"] = &opts.AdminStateUp + } + if opts.Name != "" { + l["name"] = opts.Name + } + if opts.TenantID != "" { + l["tenant_id"] = opts.TenantID + } + if opts.Persistence != nil { + l["session_persistence"] = &opts.Persistence + } + + return map[string]interface{}{"pool": l}, nil +} + +// Create accepts a CreateOpts struct and uses the values to create a new +// load balancer pool. +func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { + var res CreateResult + + reqBody, err := opts.ToPoolCreateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil) + return res +} + +// Get retrieves a particular pool based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Get(resourceURL(c, id), &res.Body, nil) + return res +} + +// UpdateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Update operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type UpdateOptsBuilder interface { + ToPoolUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is the common options struct used in this package's Update +// operation. +type UpdateOpts poolOpts + +// ToPoolUpdateMap casts a UpdateOpts struct to a map. +func (opts UpdateOpts) ToPoolUpdateMap() (map[string]interface{}, error) { + l := make(map[string]interface{}) + allowedLBMethod := map[LBMethod]bool{LBMethodRoundRobin: true, LBMethodLeastConnections: true, LBMethodSourceIp: true} + + if opts.LBMethod != "" { + if allowedLBMethod[opts.LBMethod] { + l["lb_algorithm"] = opts.LBMethod + } else { + return nil, errValidLBMethodRequired + } + } + if opts.Name != "" { + l["name"] = opts.Name + } + if opts.Description != "" { + l["description"] = opts.Description + } + if opts.AdminStateUp != nil { + l["admin_state_up"] = &opts.AdminStateUp + } + + return map[string]interface{}{"pool": l}, nil +} + +// Update allows pools to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult { + var res UpdateResult + + reqBody, err := opts.ToPoolUpdateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return res +} + +// Delete will permanently delete a particular pool based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { + var res DeleteResult + _, res.Err = c.Delete(resourceURL(c, id), nil) + return res +} + +// CreateOpts contains all the values needed to create a new Member for a Pool. +type memberOpts struct { + // Optional. Name of the Member. + Name string + + // Only required if the caller has an admin role and wants to create a Member + // for another tenant. + TenantID string + + // Required. The IP address of the member to receive traffic from the load balancer. + Address string + + // Required. The port on which to listen for client traffic. + ProtocolPort int + + // 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. + Weight int + + // Optional. If you omit this parameter, LBaaS uses the vip_subnet_id + // parameter value for the subnet UUID. + SubnetID string + + // Optional. The administrative state of the Pool. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool +} + +// MemberListOptsBuilder allows extensions to add additional parameters to the +// Member List request. +type MemberListOptsBuilder interface { + ToMemberListQuery() (string, error) +} + +// MemberListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the Member attributes you want to see returned. SortKey allows you to +// sort by a particular Member attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type MemberListOpts struct { + Name string `q:"name"` + Weight int `q:"weight"` + AdminStateUp *bool `q:"admin_state_up"` + TenantID string `q:"tenant_id"` + Address string `q:"address"` + ProtocolPort int `q:"protocol_port"` + ID string `q:"id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToMemberListQuery formats a ListOpts into a query string. +func (opts MemberListOpts) ToMemberListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// List returns a Pager which allows you to iterate over a collection of +// members. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those members that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func ListAssociateMembers(c *gophercloud.ServiceClient, poolID string, opts MemberListOptsBuilder) pagination.Pager { + url := memberRootURL(c, poolID) + if opts != nil { + query, err := opts.ToMemberListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return MemberPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +var ( + errPoolIdRequired = fmt.Errorf("PoolID is required") + errAddressRequired = fmt.Errorf("Address is required") + errProtocolPortRequired = fmt.Errorf("ProtocolPort is required") +) + +// MemberCreateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Create operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type MemberCreateOptsBuilder interface { + ToMemberCreateMap() (map[string]interface{}, error) +} + +// MemberCreateOpts is the common options struct used in this package's Create +// operation. +type MemberCreateOpts memberOpts + +// ToMemberCreateMap casts a CreateOpts struct to a map. +func (opts MemberCreateOpts) ToMemberCreateMap() (map[string]interface{}, error) { + l := make(map[string]interface{}) + + if opts.Address != "" { + l["address"] = opts.Address + } else { + return nil, errAddressRequired + } + if opts.ProtocolPort != 0 { + l["protocol_port"] = opts.ProtocolPort + } else { + return nil, errProtocolPortRequired + } + if opts.AdminStateUp != nil { + l["admin_state_up"] = &opts.AdminStateUp + } + if opts.Name != "" { + l["name"] = opts.Name + } + if opts.TenantID != "" { + l["tenant_id"] = opts.TenantID + } + if opts.SubnetID != "" { + l["subnet_id"] = opts.SubnetID + } + if opts.Weight != 0 { + l["weight"] = opts.Weight + } + + return map[string]interface{}{"member": l}, nil +} + +// CreateAssociateMember will create and associate a Member with a particular Pool. +func CreateAssociateMember(c *gophercloud.ServiceClient, poolID string, opts MemberCreateOpts) AssociateResult { + var res AssociateResult + + if poolID == "" { + res.Err = errPoolIdRequired + return res + } + + reqBody, err := opts.ToMemberCreateMap() + if err != nil { + res.Err = err + return res + } + + _, res.Err = c.Post(memberRootURL(c, poolID), reqBody, &res.Body, nil) + return res +} + +// Get retrieves a particular Pool Member based on its unique ID. +func GetAssociateMember(c *gophercloud.ServiceClient, poolID string, memberID string) GetResult { + var res GetResult + _, res.Err = c.Get(memberResourceURL(c, poolID, memberID), &res.Body, nil) + return res +} + +// MemberUpdateOptsBuilder is the interface options structs have to satisfy in order +// to be used in the main Update operation in this package. Since many +// extensions decorate or modify the common logic, it is useful for them to +// satisfy a basic interface in order for them to be used. +type MemberUpdateOptsBuilder interface { + ToMemberUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is the common options struct used in this package's Update +// operation. +type MemberUpdateOpts memberOpts + +// ToMemberUpdateMap casts a UpdateOpts struct to a map. +func (opts MemberUpdateOpts) ToMemberUpdateMap() (map[string]interface{}, error) { + l := make(map[string]interface{}) + + if opts.AdminStateUp != nil { + l["admin_state_up"] = &opts.AdminStateUp + } + if opts.Name != "" { + l["name"] = opts.Name + } + if opts.Weight != 0 { + l["weight"] = opts.Weight + } + + return map[string]interface{}{"member": l}, nil +} + +// Update allows Member to be updated. +func UpdateAssociateMember(c *gophercloud.ServiceClient, poolID string, memberID string, opts MemberUpdateOpts) UpdateResult { + var res UpdateResult + + reqBody, err := opts.ToMemberUpdateMap() + if err != nil { + res.Err = err + return res + } + + // Send request to API + _, res.Err = c.Put(memberResourceURL(c, poolID, memberID), reqBody, &res.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + return res +} + +// DisassociateMember will remove and disassociate a Member from a particular Pool. +func DeleteMember(c *gophercloud.ServiceClient, poolID string, memberID string) DeleteResult { + var res DeleteResult + _, res.Err = c.Delete(memberResourceURL(c, poolID, memberID), nil) + return res +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go new file mode 100644 index 000000000000..17f677ddf7fb --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go @@ -0,0 +1,274 @@ +package pools + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" + "github.com/rackspace/gophercloud/pagination" +) + +// SessionPersistence represents the session persistence feature of the load +// balancing service. It attempts to force connections or requests in the same +// session to be processed by the same member as long as it is ative. Three +// types of persistence are supported: +// +// SOURCE_IP: With this mode, all connections originating from the same source +// IP address, will be handled by the same Member of the Pool. +// HTTP_COOKIE: With this persistence mode, the load balancing function will +// create a cookie on the first request from a client. Subsequent +// requests containing the same cookie value will be handled by +// the same Member of the Pool. +// APP_COOKIE: With this persistence mode, the load balancing function will +// rely on a cookie established by the backend application. All +// requests carrying the same cookie value will be handled by the +// same Member of the Pool. +type SessionPersistence struct { + // The type of persistence mode + Type string `mapstructure:"type" json:"type"` + + // Name of cookie if persistence mode is set appropriately + CookieName string `mapstructure:"cookie_name" json:"cookie_name,omitempty"` +} + +type LoadBalancerID struct { + ID string `mapstructure:"id" json:"id"` +} + +type ListenerID struct { + ID string `mapstructure:"id" json:"id"` +} + +// Pool represents a logical set of devices, such as web servers, that you +// group together to receive and process traffic. The load balancing function +// chooses a Member of the Pool according to the configured load balancing +// method to handle the new requests or connections received on the VIP address. +type Pool struct { + // The load-balancer algorithm, which is round-robin, least-connections, and + // so on. This value, which must be supported, is dependent on the provider. + // Round-robin must be supported. + LBMethod string `json:"lb_algorithm" mapstructure:"lb_algorithm"` + + // The protocol of the Pool, which is TCP, HTTP, or HTTPS. + Protocol string + + // Description for the Pool. + Description string + + // A list of listeners objects IDs. + Listeners []ListenerID `mapstructure:"listeners" json:"listeners"` //[]map[string]interface{} + + // A list of member objects IDs. + Members []Member `mapstructure:"members" json:"members"` + + // The ID of associated health monitor. + MonitorID string `json:"healthmonitor_id" mapstructure:"healthmonitor_id"` + + // The network on which the members of the Pool will be located. Only members + // that are on this network can be added to the Pool. + SubnetID string `json:"subnet_id" mapstructure:"subnet_id"` + + // Owner of the Pool. Only an administrative user can specify a tenant ID + // other than its own. + TenantID string `json:"tenant_id" mapstructure:"tenant_id"` + + // The administrative state of the Pool, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"` + + // Pool name. Does not have to be unique. + Name string + + // The unique ID for the Pool. + ID string + + // A list of load balancer objects IDs. + Loadbalancers []LoadBalancerID `mapstructure:"loadbalancers" json:"loadbalancers"` + + // Indicates whether connections in the same session will be processed by the + // same Pool member or not. + Persistence SessionPersistence `mapstructure:"session_persistence" json:"session_persistence"` + + // The provider + Provider string + + Monitor monitors.Monitor `mapstructure:"healthmonitor" json:"healthmonitor"` +} + +// PoolPage is the page returned by a pager when traversing over a +// collection of pools. +type PoolPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of pools has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (p PoolPage) NextPageURL() (string, error) { + type resp struct { + Links []gophercloud.Link `mapstructure:"pools_links"` + } + + var r resp + err := mapstructure.Decode(p.Body, &r) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(r.Links) +} + +// IsEmpty checks whether a PoolPage struct is empty. +func (p PoolPage) IsEmpty() (bool, error) { + is, err := ExtractPools(p) + if err != nil { + return true, nil + } + return len(is) == 0, nil +} + +// ExtractPools accepts a Page struct, specifically a RouterPage struct, +// and extracts the elements into a slice of Router structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractPools(page pagination.Page) ([]Pool, error) { + var resp struct { + Pools []Pool `mapstructure:"pools" json:"pools"` + } + + err := mapstructure.Decode(page.(PoolPage).Body, &resp) + + return resp.Pools, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a router. +func (r commonResult) Extract() (*Pool, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Pool *Pool `json:"pool"` + } + + err := mapstructure.Decode(r.Body, &res) + + return res.Pool, err +} + +// Member represents the application running on a backend server. +type Member struct { + // Name of the Member. + Name string `json:"name" mapstructure:"name"` + + // Weight of Member. + Weight int `json:"weight" mapstructure:"weight"` + + // The administrative state of the member, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"` + + // Owner of the Member. Only an administrative user can specify a tenant ID + // other than its own. + TenantID string `json:"tenant_id" mapstructure:"tenant_id"` + + // parameter value for the subnet UUID. + SubnetID string `json:"subnet_id" mapstructure:"subnet_id"` + + // The Pool to which the Member belongs. + PoolID string `json:"pool_id" mapstructure:"pool_id"` + + // The IP address of the Member. + Address string `json:"address" mapstructure:"address"` + + // The port on which the application is hosted. + ProtocolPort int `json:"protocol_port" mapstructure:"protocol_port"` + + // The unique ID for the Member. + ID string +} + +// MemberPage is the page returned by a pager when traversing over a +// collection of Members in a Pool. +type MemberPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of members has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (p MemberPage) NextPageURL() (string, error) { + type resp struct { + Links []gophercloud.Link `mapstructure:"members_links"` + } + + var r resp + err := mapstructure.Decode(p.Body, &r) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(r.Links) +} + +// IsEmpty checks whether a MemberPage struct is empty. +func (p MemberPage) IsEmpty() (bool, error) { + is, err := ExtractMembers(p) + if err != nil { + return true, nil + } + return len(is) == 0, nil +} + +// ExtractMembers accepts a Page struct, specifically a RouterPage struct, +// and extracts the elements into a slice of Router structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractMembers(page pagination.Page) ([]Member, error) { + var resp struct { + Member []Member `mapstructure:"members" json:"members"` + } + + err := mapstructure.Decode(page.(MemberPage).Body, &resp) + + return resp.Member, err +} + +// ExtractMember is a function that accepts a result and extracts a router. +func (r commonResult) ExtractMember() (*Member, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + Member *Member `json:"member"` + } + + err := mapstructure.Decode(r.Body, &res) + + return res.Member, err +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} + +// AssociateResult represents the result of an association operation. +type AssociateResult struct { + commonResult +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/urls.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/urls.go new file mode 100644 index 000000000000..e206406bd15d --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/urls.go @@ -0,0 +1,25 @@ +package pools + +import "github.com/rackspace/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "pools" + memberPath = "members" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} + +func memberRootURL(c *gophercloud.ServiceClient, poolId string) string { + return c.ServiceURL(rootPath, resourcePath, poolId, memberPath) +} + +func memberResourceURL(c *gophercloud.ServiceClient, poolID string, memeberID string) string { + return c.ServiceURL(rootPath, resourcePath, poolID, memberPath, memeberID) +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/fixtures.go b/vendor/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/fixtures.go index 7a6e6e1ee776..d2ea18c1d54d 100644 --- a/vendor/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/fixtures.go +++ b/vendor/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/fixtures.go @@ -125,6 +125,24 @@ func HandleCreateTextObjectSuccessfully(t *testing.T, content string) { }) } +// HandleCreateTextWithCacheControlSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler +// mux that responds with a `Create` response. A Cache-Control of `max-age="3600", public` is expected. +func HandleCreateTextWithCacheControlSuccessfully(t *testing.T, content string) { + th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Cache-Control", `max-age="3600", public`) + th.TestHeader(t, r, "Accept", "application/json") + + hash := md5.New() + io.WriteString(hash, content) + localChecksum := hash.Sum(nil) + + w.Header().Set("ETag", fmt.Sprintf("%x", localChecksum)) + w.WriteHeader(http.StatusCreated) + }) +} + // HandleCreateTypelessObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler // mux that responds with a `Create` response. No Content-Type header may be present in the request, so that server- // side content-type detection will be triggered properly. diff --git a/vendor/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/requests.go b/vendor/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/requests.go index cc5d6c61b012..a2b96eda27ad 100644 --- a/vendor/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/requests.go +++ b/vendor/github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects/requests.go @@ -155,6 +155,7 @@ type CreateOptsBuilder interface { // CreateOpts is a structure that holds parameters for creating an object. type CreateOpts struct { Metadata map[string]string + CacheControl string `h:"Cache-Control"` ContentDisposition string `h:"Content-Disposition"` ContentEncoding string `h:"Content-Encoding"` ContentLength int64 `h:"Content-Length"` diff --git a/vendor/vendor.json b/vendor/vendor.json index c9111caf875c..07b0d4845a0d 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1108,279 +1108,6 @@ "path": "github.com/pkg/errors", "revision": "42fa80f2ac6ed17a977ce826074bd3009593fa9d" }, - { - "checksumSHA1": "4Q/JrLYYcwGRPVpd6sJ5yW/E9GQ=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "gltXWIbMCLQ27gCSv9VaS3HpAhk=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "gmzAuay4zJhVck2kZaPgKGZ8rLU=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "YDsUjMTIX7JIquqMdB2rpNvOYEw=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "lxRP3M3GUh35Bb+f8kP/fByBWBo=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "kCkaqHFDBuZem57TEmfCpCNeUWs=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "A8CBSjxtNeXBtM3M51qqs5FMVNE=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "TK3yB7n99AUof19ej+61dcgeEvo=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "CPt0Oy9AuqIpvS+dE4Mktb5Ia60=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "uhJIkUvunG8D06eKjdILg+w7A/k=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/servergroups", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "3TlySPDYsQjLkwHzvWf+bEoeKRU=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/tenantnetworks", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "QbXm7h4qu7+A/D6y40XHN4/QUOg=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "C2K3kYQgwa65cVRvWrNZACQFX7Q=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/compute/v2/flavors", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "N7MHp4Vh095Q0zSJD3/ZgHWTHAU=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/compute/v2/images", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "+GkNX1ZNVOdbevUkfJUt/xGPZU0=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/compute/v2/servers", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "kBNUmTmGZgMc7RA8b/MfF8mNLrw=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/identity/v2/tenants", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "QWhBGvvt47Wpw4qf3bt/rVgXdyw=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/identity/v2/tokens", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "MKnoVPcRvTrooZtNQwOnJy0bglc=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/identity/v3/tokens", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "iv6vxAhsrWtAQ2JMYh2ZxuP6yUY=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "Ov2A4adPjxO4PIcyYxnMSs7CmE0=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "w2TnbyEXzN8im5gVbcJDaNdGUdM=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "auiGgiAaicHX8JcNN+CTEEs1mgQ=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "AbS0T9msZuTFmON0HecmnwVAaUI=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "yNQNbZAHt4Y4lX5b3oFuoJ7Xpi4=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "zzzNS1HlIoHBjiNT6eOccUxyUDg=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "QSSFu2FGtwTQPeuKHIfwcidfVqM=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "Cnh0CpIejol279pKdR8RjE3s8p4=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "xUTTsfCAWNhd0LA9Jj14hCEhq8Y=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "geb6kqfYLhI4jxaUjyjtrZxwOfw=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "LuEBbhG7jYZ6P7yUASISjs6tboE=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/networks", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "3dwh+/nX7iknJ0k8B4MnxvQWAYw=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/ports", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "6aSXoX7T+hfPeS9W1pFV8eO9GFM=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/networking/v2/subnets", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "SVLhcb5xn1rL88OuZixL9tAFvfc=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "jFKg8AdD75nOKzeYmKUz6doqxU0=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "HeAr0Ae/k5vrDPfE1rI4m7eN6lU=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "e4ussrqujJHizWbdBaLjIpWYmgY=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/openstack/utils", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "YZ3h9gY2+9Cc11grugfLr9JASyU=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/pagination", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "U2yzK8GFNeHZqcoeotcHmK57lAI=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/testhelper", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, - { - "checksumSHA1": "fXtvTbQBML8QLu/qpD9sAt53J00=", - "comment": "v1.0.0-905-gadc2065", - "path": "github.com/rackspace/gophercloud/testhelper/client", - "revision": "67139b9485d6fd682c5314e963b0915e18f7947a", - "revisionTime": "2016-06-01T11:32:05Z" - }, { "path": "github.com/rainycape/unidecode", "revision": "cb7f23ec59bec0d61b19c56cd88cee3d0cc1870c" @@ -1638,6 +1365,265 @@ "comment": "v1.8.5", "path": "gopkg.in/ini.v1", "revision": "77178f22699a4ecafce485fb8d86b7afeb7e3e28" + }, + { + "checksumSHA1": "9y6a4i+MNtKDGCbRLtZUCCgra5o=", + "path": "github.com/rackspace/gophercloud", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "NGUTP3ljMt7yrbec44cS1snPtTo=", + "path": "github.com/rackspace/gophercloud/openstack", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "KXOy+EDMJgyZT0MoTXMhcFZVgNM=", + "path": "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "uh4DrOO+91jmxNlamBoloURUPO0=", + "path": "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/testing", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "ptDng4MELpgmyMbKx7q46euXYck=", + "path": "github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "GCDblO2RKSQRRQQFCUPVG3Okjg8=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "Jpju6sEOCeMRL9SP+zy86ydhPDo=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "I9QKv1AKFpSzX/O3KT6BSFat+tk=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "mA4UpOZ0dWxMKkzZqSJ3yzmDGng=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "foERSFhAuTWCXRL11GUOX+uWiaI=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "uNrf4iwP1ABn2ArI1mUS7jwnTQo=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/servergroups", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "rHEOEAm10HDsfBLU8FqKSUdwqFY=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/tenantnetworks", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "yMt7J+JXn8fC/b8qQLyeJYK6Kyc=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "/CUwRPw3GTR4kDDyhlevEJOz8VM=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/testing", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "evzUqx0LmT1wg1XLJBvfaDhb6Pc=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/flavors", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "il+znRRlkKOSfP+hxrz+0IAb2FU=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/images", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "LMxGZMRGX1MK7qaPmamj7cDJLbU=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/servers", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "gJg3QKHt4wCjM5kUjUZnXGlL57c=", + "path": "github.com/rackspace/gophercloud/openstack/identity/v2/tenants", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "6R8yUmY2FsIYn5rjvBEx4eZuVfQ=", + "path": "github.com/rackspace/gophercloud/openstack/identity/v2/tokens", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "mGcd8xg4aongnWHTgjR7kkbyOeQ=", + "path": "github.com/rackspace/gophercloud/openstack/identity/v3/tokens", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "EMdUpMYV9vQAEtBkpUdSqYaYBVc=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/common", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "CIMIys+Dh/kkLj4i+Xyn7CUDTyM=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "Dq/QBMYHUlISQXYrDCltTaF2HKU=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "NppejKsGwws2GmxajE6ZZVuP61M=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "2zGAc21qrvbg0BWMqb6ADhiIJvc=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "N1I73FBojC4D1kiASMHBLhYNV5A=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "qqJpW4mn6W25zzaXgVS9FeRYmlU=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "YaxzH3aa+FSPo7kt5wVxJfKFE04=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "61RDejBCkPnueotbY/2uVzCPt/Y=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "dD5y2ImtufAHjyVFdShtBhtAWkA=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "fChoykh7X7OjiV29nV+pl3k6y3Y=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z", + "tree": true + }, + { + "checksumSHA1": "VlAkiJAb6yIWthZpDM/hNXBzmo4=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "CKBV6wraj8j/fwkUllbry6kx/TE=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "3p4PfOfnJ9ZpqpbkgP2hpe/ZhCs=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/networks", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "UGPFc7aRab78XkJp9/c0MUfVy3I=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/ports", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "23UqLsJHksZiopcSwWmkrX3e0CY=", + "path": "github.com/rackspace/gophercloud/openstack/networking/v2/subnets", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "HecTWfPTprklkbLV5wOycG/PiGA=", + "path": "github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "bCETKrDen/3DNnzvR0fTr91Sq+k=", + "path": "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "gaOb5FwWD06v8fAyHqcejyD7wHY=", + "path": "github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "WkgQ5ZIv6/KQlNfweJd+889nWPk=", + "path": "github.com/rackspace/gophercloud/openstack/utils", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "6YVL0nw4z/n45Mx2SXTsQ8N1LYk=", + "path": "github.com/rackspace/gophercloud/pagination", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "U2yzK8GFNeHZqcoeotcHmK57lAI=", + "path": "github.com/rackspace/gophercloud/testhelper", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, + { + "checksumSHA1": "fXtvTbQBML8QLu/qpD9sAt53J00=", + "path": "github.com/rackspace/gophercloud/testhelper/client", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" } ], "rootPath": "github.com/hashicorp/terraform" 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 @@