Commit bb1ae129 authored by Davide Agnello's avatar Davide Agnello

Vendor/Godep updates

parent eb32bf55
...@@ -1615,133 +1615,158 @@ ...@@ -1615,133 +1615,158 @@
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud", "ImportPath": "github.com/rackspace/gophercloud",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack", "ImportPath": "github.com/rackspace/gophercloud/openstack",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes", "ImportPath": "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume", "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/diskconfig", "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/diskconfig",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach", "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/flavors", "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/flavors",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/images", "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/images",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/servers", "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/servers",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tenants", "ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tenants",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tokens", "ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tokens",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v3/tokens", "ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v3/tokens",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips", "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members", "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors", "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools", "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips", "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners",
"Comment": "v1.0.0-920-g934dbf8",
"Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers",
"Comment": "v1.0.0-920-g934dbf8",
"Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors",
"Comment": "v1.0.0-920-g934dbf8",
"Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools",
"Comment": "v1.0.0-920-g934dbf8",
"Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/ports",
"Comment": "v1.0.0-920-g934dbf8",
"Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/utils", "ImportPath": "github.com/rackspace/gophercloud/openstack/utils",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/pagination", "ImportPath": "github.com/rackspace/gophercloud/pagination",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/rackspace", "ImportPath": "github.com/rackspace/gophercloud/rackspace",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/rackspace/blockstorage/v1/volumes", "ImportPath": "github.com/rackspace/gophercloud/rackspace/blockstorage/v1/volumes",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/rackspace/compute/v2/servers", "ImportPath": "github.com/rackspace/gophercloud/rackspace/compute/v2/servers",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach", "ImportPath": "github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/rackspace/identity/v2/tokens", "ImportPath": "github.com/rackspace/gophercloud/rackspace/identity/v2/tokens",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/testhelper", "ImportPath": "github.com/rackspace/gophercloud/testhelper",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/testhelper/client", "ImportPath": "github.com/rackspace/gophercloud/testhelper/client",
"Comment": "v1.0.0-842-g8992d74", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/robfig/cron", "ImportPath": "github.com/robfig/cron",
......
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
...@@ -42,6 +42,11 @@ type AuthOptions struct { ...@@ -42,6 +42,11 @@ type AuthOptions struct {
// re-authenticate automatically if/when your token expires. If you set it to // re-authenticate automatically if/when your token expires. If you set it to
// false, it will not cache these settings, but re-authentication will not be // false, it will not cache these settings, but re-authentication will not be
// possible. This setting defaults to false. // possible. This setting defaults to false.
//
// NOTE: The reauth function will try to re-authenticate endlessly if left unchecked.
// The way to limit the number of attempts is to provide a custom HTTP client to the provider client
// and provide a transport that implements the RoundTripper interface and stores the number of failed retries.
// For an example of this, see here: https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
AllowReauth bool AllowReauth bool
// TokenID allows users to authenticate (possibly as another user) with an // TokenID allows users to authenticate (possibly as another user) with an
......
...@@ -134,13 +134,17 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, options gopherc ...@@ -134,13 +134,17 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
v3Client.Endpoint = endpoint v3Client.Endpoint = endpoint
} }
// copy the auth options to a local variable that we can change. `options`
// needs to stay as-is for reauth purposes
v3Options := options
var scope *tokens3.Scope var scope *tokens3.Scope
if options.TenantID != "" { if options.TenantID != "" {
scope = &tokens3.Scope{ scope = &tokens3.Scope{
ProjectID: options.TenantID, ProjectID: options.TenantID,
} }
options.TenantID = "" v3Options.TenantID = ""
options.TenantName = "" v3Options.TenantName = ""
} else { } else {
if options.TenantName != "" { if options.TenantName != "" {
scope = &tokens3.Scope{ scope = &tokens3.Scope{
...@@ -148,11 +152,11 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, options gopherc ...@@ -148,11 +152,11 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
DomainID: options.DomainID, DomainID: options.DomainID,
DomainName: options.DomainName, DomainName: options.DomainName,
} }
options.TenantName = "" v3Options.TenantName = ""
} }
} }
result := tokens3.Create(v3Client, options, scope) result := tokens3.Create(v3Client, v3Options, scope)
token, err := result.ExtractToken() token, err := result.ExtractToken()
if err != nil { if err != nil {
...@@ -277,6 +281,29 @@ func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.Endpoi ...@@ -277,6 +281,29 @@ func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.Endpoi
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// 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")
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
}
// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1 // NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
// CDN service. // CDN service.
func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
......
...@@ -51,6 +51,8 @@ type Image struct { ...@@ -51,6 +51,8 @@ type Image struct {
Status string Status string
Updated string Updated string
Metadata map[string]string
} }
// ImagePage contains a single page of results from a List operation. // ImagePage contains a single page of results from a List operation.
......
...@@ -25,6 +25,9 @@ type Monitor struct { ...@@ -25,6 +25,9 @@ type Monitor struct {
// The unique ID for the VIP. // The unique ID for the VIP.
ID string ID string
// Monitor name. Does not have to be unique.
Name string
// Owner of the VIP. Only an administrative user can specify a tenant ID // Owner of the VIP. Only an administrative user can specify a tenant ID
// other than its own. // other than its own.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"` TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
......
...@@ -74,6 +74,9 @@ type CreateOpts struct { ...@@ -74,6 +74,9 @@ type CreateOpts struct {
// current specification supports LBMethodRoundRobin and // current specification supports LBMethodRoundRobin and
// LBMethodLeastConnections as valid values for this attribute. // LBMethodLeastConnections as valid values for this attribute.
LBMethod string LBMethod string
// The provider of the pool
Provider string
} }
// Create accepts a CreateOpts struct and uses the values to create a new // Create accepts a CreateOpts struct and uses the values to create a new
...@@ -85,6 +88,7 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { ...@@ -85,6 +88,7 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
SubnetID string `json:"subnet_id"` SubnetID string `json:"subnet_id"`
LBMethod string `json:"lb_method"` LBMethod string `json:"lb_method"`
Provider string `json:"provider,omitempty"`
} }
type request struct { type request struct {
Pool pool `json:"pool"` Pool pool `json:"pool"`
...@@ -96,6 +100,7 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { ...@@ -96,6 +100,7 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
Protocol: opts.Protocol, Protocol: opts.Protocol,
SubnetID: opts.SubnetID, SubnetID: opts.SubnetID,
LBMethod: opts.LBMethod, LBMethod: opts.LBMethod,
Provider: opts.Provider,
}} }}
var res CreateResult var res CreateResult
......
// +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)
})
}
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
}
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
}
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)
}
// +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)
})
}
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
}
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
}
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)
}
// +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)
})
}
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
}
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
}
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)
}
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
}
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)
}
// Package ports contains functionality for working with Neutron port resources.
// A port represents a virtual switch port on a logical network switch. Virtual
// instances attach their interfaces into ports. The logical port also defines
// the MAC address and the IP address(es) to be assigned to the interfaces
// plugged into them. When IP addresses are associated to a port, this also
// implies the port is associated with a subnet, as the IP address was taken
// from the allocation pool for a specific subnet.
package ports
package ports
import "fmt"
func err(str string) error {
return fmt.Errorf("%s", str)
}
var (
errNetworkIDRequired = err("A Network ID is required")
)
package ports
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
)
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToPortListQuery() (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 port attributes you want to see returned. SortKey allows you to sort
// by a particular port attribute. SortDir sets the direction, and is either
// `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
Status string `q:"status"`
Name string `q:"name"`
AdminStateUp *bool `q:"admin_state_up"`
NetworkID string `q:"network_id"`
TenantID string `q:"tenant_id"`
DeviceOwner string `q:"device_owner"`
MACAddress string `q:"mac_address"`
ID string `q:"id"`
DeviceID string `q:"device_id"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
}
// ToPortListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToPortListQuery() (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
// ports. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
//
// Default policy settings return only those ports that are owned by the tenant
// who submits the request, unless the request is submitted by a user with
// administrative rights.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(c)
if opts != nil {
query, err := opts.ToPortListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return PortPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// Get retrieves a specific port based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = c.Get(getURL(c, id), &res.Body, nil)
return res
}
// 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 {
ToPortCreateMap() (map[string]interface{}, error)
}
// CreateOpts represents the attributes used when creating a new port.
type CreateOpts struct {
NetworkID string
Name string
AdminStateUp *bool
MACAddress string
FixedIPs interface{}
DeviceID string
DeviceOwner string
TenantID string
SecurityGroups []string
AllowedAddressPairs []AddressPair
}
// ToPortCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToPortCreateMap() (map[string]interface{}, error) {
p := make(map[string]interface{})
if opts.NetworkID == "" {
return nil, errNetworkIDRequired
}
p["network_id"] = opts.NetworkID
if opts.DeviceID != "" {
p["device_id"] = opts.DeviceID
}
if opts.DeviceOwner != "" {
p["device_owner"] = opts.DeviceOwner
}
if opts.FixedIPs != nil {
p["fixed_ips"] = opts.FixedIPs
}
if opts.SecurityGroups != nil {
p["security_groups"] = opts.SecurityGroups
}
if opts.TenantID != "" {
p["tenant_id"] = opts.TenantID
}
if opts.AdminStateUp != nil {
p["admin_state_up"] = &opts.AdminStateUp
}
if opts.Name != "" {
p["name"] = opts.Name
}
if opts.MACAddress != "" {
p["mac_address"] = opts.MACAddress
}
if opts.AllowedAddressPairs != nil {
p["allowed_address_pairs"] = opts.AllowedAddressPairs
}
return map[string]interface{}{"port": p}, nil
}
// Create accepts a CreateOpts struct and creates a new network using the values
// provided. You must remember to provide a NetworkID value.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToPortCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = c.Post(createURL(c), reqBody, &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 {
ToPortUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts represents the attributes used when updating an existing port.
type UpdateOpts struct {
Name string
AdminStateUp *bool
FixedIPs interface{}
DeviceID string
DeviceOwner string
SecurityGroups []string
AllowedAddressPairs []AddressPair
}
// ToPortUpdateMap casts an UpdateOpts struct to a map.
func (opts UpdateOpts) ToPortUpdateMap() (map[string]interface{}, error) {
p := make(map[string]interface{})
if opts.DeviceID != "" {
p["device_id"] = opts.DeviceID
}
if opts.DeviceOwner != "" {
p["device_owner"] = opts.DeviceOwner
}
if opts.FixedIPs != nil {
p["fixed_ips"] = opts.FixedIPs
}
if opts.SecurityGroups != nil {
p["security_groups"] = opts.SecurityGroups
}
if opts.AdminStateUp != nil {
p["admin_state_up"] = &opts.AdminStateUp
}
if opts.Name != "" {
p["name"] = opts.Name
}
if opts.AllowedAddressPairs != nil {
p["allowed_address_pairs"] = opts.AllowedAddressPairs
}
return map[string]interface{}{"port": p}, nil
}
// Update accepts a UpdateOpts struct and updates an existing port using the
// values provided.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToPortUpdateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = c.Put(updateURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201},
})
return res
}
// Delete accepts a unique ID and deletes the port associated with it.
func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(deleteURL(c, id), nil)
return res
}
// IDFromName is a convenience function that returns a port's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
portCount := 0
portID := ""
if name == "" {
return "", fmt.Errorf("A port name must be provided.")
}
pager := List(client, nil)
pager.EachPage(func(page pagination.Page) (bool, error) {
portList, err := ExtractPorts(page)
if err != nil {
return false, err
}
for _, p := range portList {
if p.Name == name {
portCount++
portID = p.ID
}
}
return true, nil
})
switch portCount {
case 0:
return "", fmt.Errorf("Unable to find port: %s", name)
case 1:
return portID, nil
default:
return "", fmt.Errorf("Found %d ports matching %s", portCount, name)
}
}
package ports
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
type commonResult struct {
gophercloud.Result
}
// Extract is a function that accepts a result and extracts a port resource.
func (r commonResult) Extract() (*Port, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
Port *Port `json:"port"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Port, 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
}
// IP is a sub-struct that represents an individual IP.
type IP struct {
SubnetID string `mapstructure:"subnet_id" json:"subnet_id"`
IPAddress string `mapstructure:"ip_address" json:"ip_address,omitempty"`
}
type AddressPair struct {
IPAddress string `mapstructure:"ip_address" json:"ip_address,omitempty"`
MACAddress string `mapstructure:"mac_address" json:"mac_address,omitempty"`
}
// Port represents a Neutron port. See package documentation for a top-level
// description of what this is.
type Port struct {
// UUID for the port.
ID string `mapstructure:"id" json:"id"`
// Network that this port is associated with.
NetworkID string `mapstructure:"network_id" json:"network_id"`
// Human-readable name for the port. Might not be unique.
Name string `mapstructure:"name" json:"name"`
// Administrative state of port. If false (down), port does not forward packets.
AdminStateUp bool `mapstructure:"admin_state_up" json:"admin_state_up"`
// Indicates whether network is currently operational. Possible values include
// `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define additional values.
Status string `mapstructure:"status" json:"status"`
// Mac address to use on this port.
MACAddress string `mapstructure:"mac_address" json:"mac_address"`
// Specifies IP addresses for the port thus associating the port itself with
// the subnets where the IP addresses are picked from
FixedIPs []IP `mapstructure:"fixed_ips" json:"fixed_ips"`
// Owner of network. Only admin users can specify a tenant_id other than its own.
TenantID string `mapstructure:"tenant_id" json:"tenant_id"`
// Identifies the entity (e.g.: dhcp agent) using this port.
DeviceOwner string `mapstructure:"device_owner" json:"device_owner"`
// Specifies the IDs of any security groups associated with a port.
SecurityGroups []string `mapstructure:"security_groups" json:"security_groups"`
// Identifies the device (e.g., virtual server) using this port.
DeviceID string `mapstructure:"device_id" json:"device_id"`
// Identifies the list of IP addresses the port will recognize/accept
AllowedAddressPairs []AddressPair `mapstructure:"allowed_address_pairs" json:"allowed_address_pairs"`
}
// PortPage is the page returned by a pager when traversing over a collection
// of network ports.
type PortPage struct {
pagination.LinkedPageBase
}
// NextPageURL is invoked when a paginated collection of ports 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 PortPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"ports_links"`
}
var r resp
err := mapstructure.Decode(p.Body, &r)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
}
// IsEmpty checks whether a PortPage struct is empty.
func (p PortPage) IsEmpty() (bool, error) {
is, err := ExtractPorts(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
}
// ExtractPorts accepts a Page struct, specifically a PortPage struct,
// and extracts the elements into a slice of Port structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractPorts(page pagination.Page) ([]Port, error) {
var resp struct {
Ports []Port `mapstructure:"ports" json:"ports"`
}
err := mapstructure.Decode(page.(PortPage).Body, &resp)
return resp.Ports, err
}
package ports
import "github.com/rackspace/gophercloud"
func resourceURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("ports", id)
}
func rootURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("ports")
}
func listURL(c *gophercloud.ServiceClient) string {
return rootURL(c)
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return resourceURL(c, id)
}
func createURL(c *gophercloud.ServiceClient) string {
return rootURL(c)
}
func updateURL(c *gophercloud.ServiceClient, id string) string {
return resourceURL(c, id)
}
func deleteURL(c *gophercloud.ServiceClient, id string) string {
return resourceURL(c, id)
}
...@@ -138,6 +138,11 @@ func (p Pager) AllPages() (Page, error) { ...@@ -138,6 +138,11 @@ func (p Pager) AllPages() (Page, error) {
// that type. // that type.
pageType := reflect.TypeOf(testPage) pageType := reflect.TypeOf(testPage)
// if it's a single page, just return the testPage (first page)
if _, found := pageType.FieldByName("SinglePageBase"); found {
return testPage, nil
}
// Switch on the page body type. Recognized types are `map[string]interface{}`, // Switch on the page body type. Recognized types are `map[string]interface{}`,
// `[]byte`, and `[]interface{}`. // `[]byte`, and `[]interface{}`.
switch testPage.GetBody().(type) { switch testPage.GetBody().(type) {
...@@ -153,7 +158,14 @@ func (p Pager) AllPages() (Page, error) { ...@@ -153,7 +158,14 @@ func (p Pager) AllPages() (Page, error) {
key = k key = k
} }
} }
pagesSlice = append(pagesSlice, b[key].([]interface{})...) switch keyType := b[key].(type) {
case map[string]interface{}:
pagesSlice = append(pagesSlice, keyType)
case []interface{}:
pagesSlice = append(pagesSlice, b[key].([]interface{})...)
default:
return false, fmt.Errorf("Unsupported page body type: %+v", keyType)
}
return true, nil return true, nil
}) })
if err != nil { if err != nil {
......
...@@ -222,3 +222,13 @@ func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (* ...@@ -222,3 +222,13 @@ func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*
} }
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewAutoScaleV1 creates a ServiceClient that may be used to access the v1 Auto Scale service.
func NewAutoScaleV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("rax:autoscale")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment