Adding Support for LBaaS v2 - Loadbalancers
diff --git a/openstack/networking/v2/extensions/lbaas_v2/doc.go b/openstack/networking/v2/extensions/lbaas_v2/doc.go
new file mode 100644
index 0000000..ec7f9d6
--- /dev/null
+++ b/openstack/networking/v2/extensions/lbaas_v2/doc.go
@@ -0,0 +1,3 @@
+// Package lbaas_v2 provides information and interaction with the Load Balancer
+// as a Service v2 extension for the OpenStack Networking service.
+package lbaas_v2
diff --git a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go
new file mode 100644
index 0000000..d75171d
--- /dev/null
+++ b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go
@@ -0,0 +1,197 @@
+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
+
+// 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 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"`
+}
+
+// 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 LoadbalancerPage{pagination.LinkedPageBase{PageResult: r}}
+ })
+}
+
+var (
+ errVipSubnetIDRequried = fmt.Errorf("VipSubnetID is required")
+)
+
+// CreateOpts contains all the values needed to create a new Loadbalancer.
+type CreateOpts 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
+}
+
+// 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
+
+ // Validate required opts
+ if opts.VipSubnetID == "" {
+ res.Err = errVipSubnetIDRequried
+ return res
+ }
+
+ type loadbalancer struct {
+ Name *string `json:"name,omitempty"`
+ Description *string `json:"description,omitempty"`
+ VipSubnetID string `json:"vip_subnet_id"`
+ TenantID *string `json:"tenant_id,omitempty"`
+ VipAddress *string `json:"vip_address,omitempty"`
+ AdminStateUp *bool `json:"admin_state_up,omitempty"`
+ Flavor *string `json:"flavor,omitempty"`
+ Provider *string `json:"provider,omitempty"`
+ }
+
+ type request struct {
+ Loadbalancer loadbalancer `json:"loadbalancer"`
+ }
+
+ reqBody := request{Loadbalancer: loadbalancer{
+ Name: gophercloud.MaybeString(opts.Name),
+ Description: gophercloud.MaybeString(opts.Description),
+ VipSubnetID: opts.VipSubnetID,
+ TenantID: gophercloud.MaybeString(opts.TenantID),
+ VipAddress: gophercloud.MaybeString(opts.VipAddress),
+ AdminStateUp: opts.AdminStateUp,
+ Flavor: gophercloud.MaybeString(opts.Flavor),
+ Provider: gophercloud.MaybeString(opts.Provider),
+ }}
+
+ _, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
+ return res
+}
+
+// Get retrieves a particular virtual IP 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 needed to update an existing virtual
+// Loadbalancer. Attributes not listed here but appear in CreateOpts are
+// immutable and cannot be updated.
+type UpdateOpts 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
+
+ // Optional. The administrative state of the Loadbalancer. A valid value is true (UP)
+ // or false (DOWN).
+ AdminStateUp *bool
+}
+
+// Update is an operation which modifies the attributes of the specified Loadbalancer.
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
+ type loadbalancer struct {
+ Name *string `json:"name,omitempty"`
+ Description *string `json:"description,omitempty"`
+ AdminStateUp *bool `json:"admin_state_up,omitempty"`
+ }
+
+ type request struct {
+ Loadbalancer loadbalancer `json:"loadbalancer"`
+ }
+
+ reqBody := request{Loadbalancer: loadbalancer{
+ Name: gophercloud.MaybeString(opts.Name),
+ Description: gophercloud.MaybeString(opts.Description),
+ AdminStateUp: opts.AdminStateUp,
+ }}
+
+ 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 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
+}
diff --git a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests_test.go b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests_test.go
new file mode 100644
index 0000000..3ff1996
--- /dev/null
+++ b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests_test.go
@@ -0,0 +1,304 @@
+package loadbalancers
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ 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/loadbalancers", rootURL(fake.ServiceClient()))
+ th.AssertEquals(t, th.Endpoint()+"v2.0/lbaas/loadbalancers/foo", resourceURL(fake.ServiceClient(), "foo"))
+}
+
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ 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", fake.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "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": "provider_1",
+ "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": "provider_2",
+ "admin_state_up": true,
+ "provisioning_status": "PENDING_CREATE",
+ "operating_status": "OFFLINE"
+ }
+ ]
+}
+ `)
+ })
+
+ count := 0
+
+ List(fake.ServiceClient(), ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractLoadbalancers(page)
+ if err != nil {
+ t.Errorf("Failed to extract LBs: %v", err)
+ return false, err
+ }
+
+ expected := []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: "provider_1",
+ AdminStateUp: true,
+ ProvisioningStatus: "ACTIVE",
+ OperatingStatus: "ONLINE",
+ },
+ {
+ 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: "provider_2",
+ AdminStateUp: true,
+ ProvisioningStatus: "PENDING_CREATE",
+ OperatingStatus: "OFFLINE",
+ },
+ }
+
+ 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/loadbalancers", 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, `
+{
+ "loadbalancer": {
+ "name": "NewLb",
+ "vip_subnet_id": "8032909d-47a1-4715-90af-5153ffe39861",
+ "vip_address": "10.0.0.11",
+ "flavor": "small",
+ "provider": "provider_1",
+ "admin_state_up": true
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+
+ fmt.Fprintf(w, `
+{
+ "loadbalancer": {
+ "id": "04816630-0320-4f9d-a17b-402b4e145d91",
+ "tenant_id": "54030507-44f7-473c-9342-b4d14a95f692",
+ "name": "NewLb",
+ "description": "",
+ "vip_subnet_id": "8032909d-47a1-4715-90af-5153ffe39861",
+ "vip_address": "10.0.0.11",
+ "flavor": "small",
+ "provider": "provider_1",
+ "admin_state_up": true,
+ "provisioning_status": "PENDING_CREATE",
+ "operating_status": "OFFLINE"
+ }
+}
+ `)
+ })
+
+ opts := CreateOpts{
+ Name: "NewLb",
+ AdminStateUp: Up,
+ VipSubnetID: "8032909d-47a1-4715-90af-5153ffe39861",
+ VipAddress: "10.0.0.11",
+ Flavor: "small",
+ Provider: "provider_1",
+ }
+
+ r, err := Create(fake.ServiceClient(), opts).Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, "PENDING_CREATE", r.ProvisioningStatus)
+ th.AssertEquals(t, "", r.Description)
+ th.AssertEquals(t, true, r.AdminStateUp)
+ th.AssertEquals(t, "8032909d-47a1-4715-90af-5153ffe39861", r.VipSubnetID)
+ th.AssertEquals(t, "54030507-44f7-473c-9342-b4d14a95f692", r.TenantID)
+ th.AssertEquals(t, "NewLb", r.Name)
+ th.AssertEquals(t, "OFFLINE", r.OperatingStatus)
+ th.AssertEquals(t, "small", r.Flavor)
+ th.AssertEquals(t, "provider_1", r.Provider)
+ th.AssertEquals(t, "10.0.0.11", r.VipAddress)
+}
+
+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", Description: "bar"})
+ if res.Err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+ res = Create(fake.ServiceClient(), CreateOpts{Name: "foo", Description: "bar", VipAddress: "bar"})
+ 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/loadbalancers/3c073c6d-18f7-45e4-8921-84358b70542d", 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, `
+{
+ "loadbalancer": {
+ "id": "3c073c6d-18f7-45e4-8921-84358b70542d",
+ "tenant_id": "54030507-44f7-473c-9342-b4d14a95f692",
+ "name": "web_lb",
+ "description": "",
+ "vip_subnet_id": "8a49c438-848f-467b-9655-ea1548708154",
+ "vip_address": "10.30.176.47",
+ "flavor": "small",
+ "provider": "provider_1",
+ "admin_state_up": true,
+ "provisioning_status": "ACTIVE",
+ "operating_status": "ONLINE"
+ }
+}
+ `)
+ })
+
+ loadbalancer, err := Get(fake.ServiceClient(), "3c073c6d-18f7-45e4-8921-84358b70542d").Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, "ACTIVE", loadbalancer.ProvisioningStatus)
+ th.AssertEquals(t, "ONLINE", loadbalancer.OperatingStatus)
+ th.AssertEquals(t, "", loadbalancer.Description)
+ th.AssertEquals(t, true, loadbalancer.AdminStateUp)
+ th.AssertEquals(t, "web_lb", loadbalancer.Name)
+ th.AssertEquals(t, "10.30.176.47", loadbalancer.VipAddress)
+}
+
+func TestUpdate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/038d98e2-6a7d-473a-9ce4-76f7e1e0c6a4", 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, `
+{
+ "loadbalancer": {
+ "name": "NewLbName"
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusAccepted)
+
+ fmt.Fprintf(w, `
+{
+ "loadbalancer": {
+ "id": "038d98e2-6a7d-473a-9ce4-76f7e1e0c6a4",
+ "tenant_id": "54030507-44f7-473c-9342-b4d14a95f692",
+ "name": "NewLbName",
+ "description": "",
+ "vip_subnet_id": "8032909d-47a1-4715-90af-5153ffe39861",
+ "vip_address": "10.0.0.11",
+ "flavor": "small",
+ "provider": "provider_1",
+ "admin_state_up": true,
+ "provisioning_status": "PENDING_UPDATE",
+ "operating_status": "OFFLINE"
+ }
+}
+ `)
+ })
+
+ options := UpdateOpts{
+ Name: "NewLbName",
+ }
+
+ loadbalancer, err := Update(fake.ServiceClient(), "038d98e2-6a7d-473a-9ce4-76f7e1e0c6a4", options).Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, "NewLbName", loadbalancer.Name)
+}
+
+func TestDelete(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/82c54b9a-cf84-460f-bdcf-fa591e6fd6f0", 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(), "82c54b9a-cf84-460f-bdcf-fa591e6fd6f0")
+ th.AssertNoErr(t, res.Err)
+}
diff --git a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go
new file mode 100644
index 0000000..4306b06
--- /dev/null
+++ b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go
@@ -0,0 +1,128 @@
+package loadbalancers
+
+import (
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
+ "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.
+// This entity is sometimes known in LB products under the name of a "virtual
+// server", a "vserver" or a "listener".
+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"`
+}
+
+// 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
+}
+
+// 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/loadbalancers/urls.go b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go
new file mode 100644
index 0000000..48bcc39
--- /dev/null
+++ b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go
@@ -0,0 +1,16 @@
+package loadbalancers
+
+import "github.com/rackspace/gophercloud"
+
+const (
+ rootPath = "lbaas"
+ resourcePath = "loadbalancers"
+)
+
+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)
+}