Adding Support for LBaaS v2 - Listeners
diff --git a/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go b/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go
new file mode 100644
index 0000000..e903244
--- /dev/null
+++ b/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go
@@ -0,0 +1,231 @@
+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
+
+// Convenience vars for AdminStateUp values.
+var (
+ iTrue = true
+ iFalse = false
+
+ Up AdminState = &iTrue
+ Down AdminState = &iFalse
+)
+
+// 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 network 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"`
+ 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"`
+}
+
+// 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 ListOpts) pagination.Pager {
+ q, err := gophercloud.BuildQueryString(&opts)
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ u := rootURL(c) + q.String()
+ return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page {
+ return ListenerPage{pagination.LinkedPageBase{PageResult: r}}
+ })
+}
+
+var (
+ errLoadbalancerIdRequired = fmt.Errorf("Loadbalancer ID is required")
+ errProtocolRequired = fmt.Errorf("Protocol is required")
+ errProtocolPortRequired = fmt.Errorf("Protocol port is required")
+)
+
+// CreateOpts contains all the values needed to create a new Listener.
+type CreateOpts struct {
+ // Required. The protocol - can either be TCP, HTTP or HTTPS.
+ Protocol string
+
+ // 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
+}
+
+// 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
+
+ // Validate required opts
+ if opts.LoadbalancerID == "" {
+ res.Err = errLoadbalancerIdRequired
+ return res
+ }
+ if opts.Protocol == "" {
+ res.Err = errProtocolRequired
+ return res
+ }
+ if opts.ProtocolPort == 0 {
+ res.Err = errProtocolPortRequired
+ return res
+ }
+
+ type listener struct {
+ Name *string `json:"name,omitempty"`
+ LoadbalancerID string `json:"loadbalancer_id,omitempty"`
+ Protocol string `json:"protocol"`
+ ProtocolPort int `json:"protocol_port"`
+ DefaultPoolID *string `json:"default_pool_id,omitempty"`
+ Description *string `json:"description,omitempty"`
+ TenantID *string `json:"tenant_id,omitempty"`
+ ConnLimit *int `json:"connection_limit,omitempty"`
+ AdminStateUp *bool `json:"admin_state_up,omitempty"`
+ DefaultTlsContainerRef *string `json:"default_tls_container_ref,omitempty"`
+ SniContainerRefs []string `json:"sni_container_refs,omitempty"`
+ }
+
+ type request struct {
+ Listener listener `json:"listener"`
+ }
+
+ reqBody := request{Listener: listener{
+ Name: gophercloud.MaybeString(opts.Name),
+ LoadbalancerID: opts.LoadbalancerID,
+ Protocol: opts.Protocol,
+ ProtocolPort: opts.ProtocolPort,
+ DefaultPoolID: gophercloud.MaybeString(opts.DefaultPoolID),
+ Description: gophercloud.MaybeString(opts.Description),
+ TenantID: gophercloud.MaybeString(opts.TenantID),
+ ConnLimit: opts.ConnLimit,
+ AdminStateUp: opts.AdminStateUp,
+ DefaultTlsContainerRef: gophercloud.MaybeString(opts.DefaultTlsContainerRef),
+ SniContainerRefs: opts.SniContainerRefs,
+ }}
+
+ _, 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
+}
+
+// UpdateOpts contains all the values that can be updated on an existing Listener.
+// Attributes not listed here but appear in CreateOpts are immutable and cannot
+// be updated.
+type UpdateOpts struct {
+ // Human-readable name for the Listener. Does not have to be unique.
+ Name 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
+}
+
+// Update is an operation which modifies the attributes of the specified Listener.
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
+ type listener struct {
+ Name string `json:"name,omitempty"`
+ Description *string `json:"description,omitempty"`
+ ConnLimit *int `json:"connection_limit,omitempty"`
+ AdminStateUp *bool `json:"admin_state_up,omitempty"`
+ DefaultTlsContainerRef *string `json:"default_tls_container_ref,omitempty"`
+ SniContainerRefs []string `json:"sni_container_refs,omitempty"`
+ }
+
+ type request struct {
+ Listener listener `json:"listener"`
+ }
+
+ reqBody := request{Listener: listener{
+ Name: opts.Name,
+ Description: gophercloud.MaybeString(opts.Description),
+ ConnLimit: opts.ConnLimit,
+ AdminStateUp: opts.AdminStateUp,
+ DefaultTlsContainerRef: gophercloud.MaybeString(opts.DefaultTlsContainerRef),
+ SniContainerRefs: opts.SniContainerRefs,
+ }}
+
+ var res UpdateResult
+ _, 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/openstack/networking/v2/extensions/lbaas_v2/listeners/requests_test.go b/openstack/networking/v2/extensions/lbaas_v2/listeners/requests_test.go
new file mode 100644
index 0000000..dc14755
--- /dev/null
+++ b/openstack/networking/v2/extensions/lbaas_v2/listeners/requests_test.go
@@ -0,0 +1,322 @@
+package listeners
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+ //"github.com/davecgh/go-spew/spew"
+ fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
+ "github.com/rackspace/gophercloud/pagination"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestURLs(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.AssertEquals(t, th.Endpoint()+"v2.0/lbaas/listeners", rootURL(fake.ServiceClient()))
+ th.AssertEquals(t, th.Endpoint()+"v2.0/lbaas/listeners/foo", resourceURL(fake.ServiceClient(), "foo"))
+}
+
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ 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", fake.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "listeners":[
+ {
+ "id": "db902c0c-d5ff-4753-b465-668ad9656918",
+ "tenant_id": "310df60f-2a10-4ee5-9554-98393092194c",
+ "name": "web_listener",
+ "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_listener",
+ "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"]
+ }
+ ]
+}
+ `)
+ })
+
+ count := 0
+
+ List(fake.ServiceClient(), ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractListeners(page)
+ if err != nil {
+ t.Errorf("Failed to extract LBs: %v", err)
+ return false, err
+ }
+
+ expected := []Listener{
+ {
+ ID: "db902c0c-d5ff-4753-b465-668ad9656918",
+ TenantID: "310df60f-2a10-4ee5-9554-98393092194c",
+ Name: "web_listener",
+ Description: "listener config for the web tier",
+ Loadbalancers: []map[string]interface{}{{"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"},
+ },
+ {
+ ID: "36e08a3e-a78f-4b40-a229-1e7e23eee1ab",
+ TenantID: "310df60f-2a10-4ee5-9554-98393092194c",
+ Name: "db_listener",
+ Description: "listener config for the db tier",
+ Loadbalancers: []map[string]interface{}{{"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"},
+ },
+ }
+
+ th.CheckDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+
+ if count != 1 {
+ t.Errorf("Expected 1 page, got %d", count)
+ }
+}
+
+func TestCreate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ 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", fake.TokenID)
+ th.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestJSONRequest(t, r, `
+{
+ "listener": {
+ "loadbalancer_id": "53306cda-815d-4354-9444-59e09da9c3c5",
+ "protocol": "HTTP",
+ "name": "NewListener",
+ "admin_state_up": true,
+ "default_tls_container_ref": "8032909d-47a1-4715-90af-5153ffe39861",
+ "default_pool_id": "61b1f87a-7a21-4ad3-9dda-7f81d249944f",
+ "protocol_port": 80
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+
+ fmt.Fprintf(w, `
+{
+ "listener": {
+ "id": "36e08a3e-a78f-4b40-a229-1e7e23eee1ab",
+ "tenant_id": "83657cfcdfe44cd5920adaf26c48ceea",
+ "name": "NewListener",
+ "description": "",
+ "loadbalancers": [{"id": "53306cda-815d-4354-9444-59e09da9c3c5"}],
+ "protocol": "HTTP",
+ "protocol_port": 80,
+ "connection_limit": -1,
+ "default_pool_id": "61b1f87a-7a21-4ad3-9dda-7f81d249944f",
+ "admin_state_up": true,
+ "default_tls_container_ref": "8032909d-47a1-4715-90af-5153ffe39861"
+ }
+}
+ `)
+ })
+
+ opts := CreateOpts{
+ Protocol: "HTTP",
+ Name: "NewListener",
+ LoadbalancerID: "53306cda-815d-4354-9444-59e09da9c3c5",
+ AdminStateUp: Up,
+ DefaultTlsContainerRef: "8032909d-47a1-4715-90af-5153ffe39861",
+ DefaultPoolID: "61b1f87a-7a21-4ad3-9dda-7f81d249944f",
+ ProtocolPort: 80,
+ }
+
+ r, err := Create(fake.ServiceClient(), opts).Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, "HTTP", r.Protocol)
+ th.AssertEquals(t, "", r.Description)
+ th.AssertEquals(t, true, r.AdminStateUp)
+ th.AssertEquals(t, "8032909d-47a1-4715-90af-5153ffe39861", r.DefaultTlsContainerRef)
+ th.AssertEquals(t, "83657cfcdfe44cd5920adaf26c48ceea", r.TenantID)
+ th.AssertEquals(t, -1, r.ConnLimit)
+ th.AssertEquals(t, "61b1f87a-7a21-4ad3-9dda-7f81d249944f", r.DefaultPoolID)
+ th.AssertEquals(t, 80, r.ProtocolPort)
+ th.AssertEquals(t, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", r.ID)
+ th.AssertEquals(t, "53306cda-815d-4354-9444-59e09da9c3c5", r.Loadbalancers[0]["id"])
+ th.AssertEquals(t, "NewListener", r.Name)
+}
+
+func TestRequiredCreateOpts(t *testing.T) {
+ res := Create(fake.ServiceClient(), CreateOpts{})
+ if res.Err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+ res = Create(fake.ServiceClient(), CreateOpts{Name: "foo"})
+ if res.Err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+ res = Create(fake.ServiceClient(), CreateOpts{Name: "foo", TenantID: "bar"})
+ if res.Err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+ res = Create(fake.ServiceClient(), CreateOpts{Name: "foo", TenantID: "bar", Protocol: "bar"})
+ if res.Err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+ res = Create(fake.ServiceClient(), CreateOpts{Name: "foo", TenantID: "bar", Protocol: "bar", ProtocolPort: 80})
+ if res.Err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+}
+
+func TestGet(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ 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", fake.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "listener": {
+ "id": "4ec89087-d057-4e2c-911f-60a3b47ee304",
+ "tenant_id": "83657cfcdfe44cd5920adaf26c48ceea",
+ "name": "NewListener",
+ "description": "",
+ "loadbalancers": [{"id": "53306cda-815d-4354-9444-59e09da9c3c5"}],
+ "protocol": "HTTP",
+ "protocol_port": 80,
+ "connection_limit": -1,
+ "default_pool_id": "61b1f87a-7a21-4ad3-9dda-7f81d249944f",
+ "admin_state_up": true,
+ "default_tls_container_ref": "8032909d-47a1-4715-90af-5153ffe39861"
+ }
+}
+ `)
+ })
+
+ l, err := Get(fake.ServiceClient(), "4ec89087-d057-4e2c-911f-60a3b47ee304").Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, "HTTP", l.Protocol)
+ th.AssertEquals(t, "", l.Description)
+ th.AssertEquals(t, true, l.AdminStateUp)
+ th.AssertEquals(t, "8032909d-47a1-4715-90af-5153ffe39861", l.DefaultTlsContainerRef)
+ th.AssertEquals(t, "83657cfcdfe44cd5920adaf26c48ceea", l.TenantID)
+ th.AssertEquals(t, -1, l.ConnLimit)
+ th.AssertEquals(t, "61b1f87a-7a21-4ad3-9dda-7f81d249944f", l.DefaultPoolID)
+ th.AssertEquals(t, 80, l.ProtocolPort)
+ th.AssertEquals(t, "4ec89087-d057-4e2c-911f-60a3b47ee304", l.ID)
+ th.AssertEquals(t, "53306cda-815d-4354-9444-59e09da9c3c5", l.Loadbalancers[0]["id"])
+ th.AssertEquals(t, "NewListener", l.Name)
+}
+
+func TestUpdate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ 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", fake.TokenID)
+ th.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestJSONRequest(t, r, `
+{
+ "listener": {
+ "name": "NewListenerName",
+ "connection_limit": 1001
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusAccepted)
+
+ fmt.Fprintf(w, `
+{
+ "listener": {
+ "id": "4ec89087-d057-4e2c-911f-60a3b47ee304",
+ "tenant_id": "83657cfcdfe44cd5920adaf26c48ceea",
+ "name": "NewListenerName",
+ "description": "",
+ "loadbalancers": [{"id": "53306cda-815d-4354-9444-59e09da9c3c5"}],
+ "protocol": "HTTP",
+ "protocol_port": 80,
+ "connection_limit": 1001,
+ "default_pool_id": "61b1f87a-7a21-4ad3-9dda-7f81d249944f",
+ "admin_state_up": true,
+ "default_tls_container_ref": "8032909d-47a1-4715-90af-5153ffe39861"
+ }
+}
+ `)
+ })
+
+ i1001 := 1001
+ options := UpdateOpts{
+ Name: "NewListenerName",
+ ConnLimit: &i1001,
+ }
+
+ l, err := Update(fake.ServiceClient(), "4ec89087-d057-4e2c-911f-60a3b47ee304", options).Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, *(options.ConnLimit), l.ConnLimit)
+ th.AssertEquals(t, options.Name, l.Name)
+}
+
+func TestDelete(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ 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", fake.TokenID)
+ w.WriteHeader(http.StatusNoContent)
+ })
+
+ res := Delete(fake.ServiceClient(), "4ec89087-d057-4e2c-911f-60a3b47ee304")
+ th.AssertNoErr(t, res.Err)
+}
diff --git a/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go b/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go
new file mode 100644
index 0000000..76b374c
--- /dev/null
+++ b/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go
@@ -0,0 +1,133 @@
+package listeners
+
+import (
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+ // "github.com/davecgh/go-spew/spew"
+)
+
+// 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 objects IDs.
+ Loadbalancers []map[string]interface{} `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"`
+}
+
+// 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/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go b/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go
new file mode 100644
index 0000000..b4cb90b
--- /dev/null
+++ b/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)
+}