Update vendor dir and Godeps.json with new Godep

This commit is contained in:
saadali
2016-05-11 16:59:55 -07:00
parent b83af3d481
commit c708e2cc82
2053 changed files with 955 additions and 140589 deletions

View File

@@ -1,131 +0,0 @@
package snapshots
import (
"errors"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
os "github.com/rackspace/gophercloud/openstack/blockstorage/v1/snapshots"
)
func updateURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("snapshots", id)
}
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToSnapshotCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains options for creating a Snapshot. This object is passed to
// the snapshots.Create function. For more information about these parameters,
// see the Snapshot object.
type CreateOpts struct {
// REQUIRED
VolumeID string
// OPTIONAL
Description string
// OPTIONAL
Force bool
// OPTIONAL
Name string
}
// ToSnapshotCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToSnapshotCreateMap() (map[string]interface{}, error) {
s := make(map[string]interface{})
if opts.VolumeID == "" {
return nil, errors.New("Required CreateOpts field 'VolumeID' not set.")
}
s["volume_id"] = opts.VolumeID
if opts.Description != "" {
s["display_description"] = opts.Description
}
if opts.Name != "" {
s["display_name"] = opts.Name
}
if opts.Force {
s["force"] = opts.Force
}
return map[string]interface{}{"snapshot": s}, nil
}
// Create will create a new Snapshot based on the values in CreateOpts. To
// extract the Snapshot object from the response, call the Extract method on the
// CreateResult.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
return CreateResult{os.Create(client, opts)}
}
// Delete will delete the existing Snapshot with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) os.DeleteResult {
return os.Delete(client, id)
}
// Get retrieves the Snapshot with the provided ID. To extract the Snapshot
// object from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
return GetResult{os.Get(client, id)}
}
// List returns Snapshots.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return os.List(client, os.ListOpts{})
}
// 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 {
ToSnapshotUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts is the common options struct used in this package's Update
// operation.
type UpdateOpts struct {
Name string
Description string
}
// ToSnapshotUpdateMap casts a UpdateOpts struct to a map.
func (opts UpdateOpts) ToSnapshotUpdateMap() (map[string]interface{}, error) {
s := make(map[string]interface{})
if opts.Name != "" {
s["display_name"] = opts.Name
}
if opts.Description != "" {
s["display_description"] = opts.Description
}
return map[string]interface{}{"snapshot": s}, nil
}
// Update accepts a UpdateOpts struct and updates an existing snapshot using the
// values provided.
func Update(c *gophercloud.ServiceClient, snapshotID string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToSnapshotUpdateMap()
if err != nil {
res.Err = err
return res
}
// Send request to API
_, res.Err = c.Request("PUT", updateURL(c, snapshotID), gophercloud.RequestOpts{
JSONBody: &reqBody,
JSONResponse: &res.Body,
OkCodes: []int{200, 201},
})
return res
}

View File

@@ -1,3 +0,0 @@
// Package snapshots provides information and interaction with the snapshot
// API resource for the Rackspace Block Storage service.
package snapshots

View File

@@ -1,147 +0,0 @@
package snapshots
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/blockstorage/v1/snapshots"
"github.com/rackspace/gophercloud/pagination"
"github.com/mitchellh/mapstructure"
)
// Status is the type used to represent a snapshot's status
type Status string
// Constants to use for supported statuses
const (
Creating Status = "CREATING"
Available Status = "AVAILABLE"
Deleting Status = "DELETING"
Error Status = "ERROR"
DeleteError Status = "ERROR_DELETING"
)
// Snapshot is the Rackspace representation of an external block storage device.
type Snapshot struct {
// The timestamp when this snapshot was created.
CreatedAt string `mapstructure:"created_at"`
// The human-readable description for this snapshot.
Description string `mapstructure:"display_description"`
// The human-readable name for this snapshot.
Name string `mapstructure:"display_name"`
// The UUID for this snapshot.
ID string `mapstructure:"id"`
// The random metadata associated with this snapshot. Note: unlike standard
// OpenStack snapshots, this cannot actually be set.
Metadata map[string]string `mapstructure:"metadata"`
// Indicates the current progress of the snapshot's backup procedure.
Progress string `mapstructure:"os-extended-snapshot-attributes:progress"`
// The project ID.
ProjectID string `mapstructure:"os-extended-snapshot-attributes:project_id"`
// The size of the volume which this snapshot backs up.
Size int `mapstructure:"size"`
// The status of the snapshot.
Status Status `mapstructure:"status"`
// The ID of the volume which this snapshot seeks to back up.
VolumeID string `mapstructure:"volume_id"`
}
// CreateResult represents the result of a create operation
type CreateResult struct {
os.CreateResult
}
// GetResult represents the result of a get operation
type GetResult struct {
os.GetResult
}
// UpdateResult represents the result of an update operation
type UpdateResult struct {
gophercloud.Result
}
func commonExtract(resp interface{}, err error) (*Snapshot, error) {
if err != nil {
return nil, err
}
var respStruct struct {
Snapshot *Snapshot `json:"snapshot"`
}
err = mapstructure.Decode(resp, &respStruct)
return respStruct.Snapshot, err
}
// Extract will get the Snapshot object out of the GetResult object.
func (r GetResult) Extract() (*Snapshot, error) {
return commonExtract(r.Body, r.Err)
}
// Extract will get the Snapshot object out of the CreateResult object.
func (r CreateResult) Extract() (*Snapshot, error) {
return commonExtract(r.Body, r.Err)
}
// Extract will get the Snapshot object out of the UpdateResult object.
func (r UpdateResult) Extract() (*Snapshot, error) {
return commonExtract(r.Body, r.Err)
}
// ExtractSnapshots extracts and returns Snapshots. It is used while iterating over a snapshots.List call.
func ExtractSnapshots(page pagination.Page) ([]Snapshot, error) {
var response struct {
Snapshots []Snapshot `json:"snapshots"`
}
err := mapstructure.Decode(page.(os.ListResult).Body, &response)
return response.Snapshots, err
}
// WaitUntilComplete will continually poll a snapshot until it successfully
// transitions to a specified state. It will do this for at most the number of
// seconds specified.
func (snapshot Snapshot) WaitUntilComplete(c *gophercloud.ServiceClient, timeout int) error {
return gophercloud.WaitFor(timeout, func() (bool, error) {
// Poll resource
current, err := Get(c, snapshot.ID).Extract()
if err != nil {
return false, err
}
// Has it been built yet?
if current.Progress == "100%" {
return true, nil
}
return false, nil
})
}
// WaitUntilDeleted will continually poll a snapshot until it has been
// successfully deleted, i.e. returns a 404 status.
func (snapshot Snapshot) WaitUntilDeleted(c *gophercloud.ServiceClient, timeout int) error {
return gophercloud.WaitFor(timeout, func() (bool, error) {
// Poll resource
_, err := Get(c, snapshot.ID).Extract()
// Check for a 404
if casted, ok := err.(*gophercloud.UnexpectedResponseCodeError); ok && casted.Actual == 404 {
return true, nil
} else if err != nil {
return false, err
}
return false, nil
})
}

View File

@@ -1,18 +0,0 @@
package volumetypes
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumetypes"
"github.com/rackspace/gophercloud/pagination"
)
// List returns all volume types.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return os.List(client)
}
// Get will retrieve the volume type with the provided ID. To extract the volume
// type from the result, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
return GetResult{os.Get(client, id)}
}

View File

@@ -1,3 +0,0 @@
// Package volumetypes provides information and interaction with the volume type
// API resource for the Rackspace Block Storage service.
package volumetypes

View File

@@ -1,37 +0,0 @@
package volumetypes
import (
"github.com/mitchellh/mapstructure"
os "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumetypes"
"github.com/rackspace/gophercloud/pagination"
)
type VolumeType os.VolumeType
type GetResult struct {
os.GetResult
}
// Extract will get the Volume Type struct out of the response.
func (r GetResult) Extract() (*VolumeType, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
VolumeType *VolumeType `json:"volume_type" mapstructure:"volume_type"`
}
err := mapstructure.Decode(r.Body, &res)
return res.VolumeType, err
}
func ExtractVolumeTypes(page pagination.Page) ([]VolumeType, error) {
var response struct {
VolumeTypes []VolumeType `mapstructure:"volume_types"`
}
err := mapstructure.Decode(page.(os.ListResult).Body, &response)
return response.VolumeTypes, err
}

View File

@@ -1,18 +0,0 @@
package base
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/cdn/v1/base"
)
// Get retrieves the home document, allowing the user to discover the
// entire API.
func Get(c *gophercloud.ServiceClient) os.GetResult {
return os.Get(c)
}
// Ping retrieves a ping to the server.
func Ping(c *gophercloud.ServiceClient) os.PingResult {
return os.Ping(c)
}

View File

@@ -1,4 +0,0 @@
// Package base provides information and interaction with the base API
// resource in the Rackspace CDN service. This API resource allows for
// retrieving the Home Document and pinging the root URL.
package base

View File

@@ -1,18 +0,0 @@
package flavors
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/cdn/v1/flavors"
"github.com/rackspace/gophercloud/pagination"
)
// List returns a single page of CDN flavors.
func List(c *gophercloud.ServiceClient) pagination.Pager {
return os.List(c)
}
// Get retrieves a specific flavor based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) os.GetResult {
return os.Get(c, id)
}

View File

@@ -1,6 +0,0 @@
// Package flavors provides information and interaction with the flavors API
// resource in the Rackspace CDN service. This API resource allows for
// listing flavors and retrieving a specific flavor.
//
// A flavor is a mapping configuration to a CDN provider.
package flavors

View File

@@ -1,13 +0,0 @@
package serviceassets
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/cdn/v1/serviceassets"
)
// Delete accepts a unique ID and deletes the CDN service asset associated with
// it.
func Delete(c *gophercloud.ServiceClient, id string, opts os.DeleteOptsBuilder) os.DeleteResult {
return os.Delete(c, id, opts)
}

View File

@@ -1,7 +0,0 @@
// Package serviceassets provides information and interaction with the
// serviceassets API resource in the Rackspace CDN service. This API resource
// allows for deleting cached assets.
//
// A service distributes assets across the network. Service assets let you
// interrogate properties about these assets and perform certain actions on them.
package serviceassets

View File

@@ -1,37 +0,0 @@
package services
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/cdn/v1/services"
"github.com/rackspace/gophercloud/pagination"
)
// List returns a Pager which allows you to iterate over a collection of
// CDN services. It accepts a ListOpts struct, which allows for pagination via
// marker and limit.
func List(c *gophercloud.ServiceClient, opts os.ListOptsBuilder) pagination.Pager {
return os.List(c, opts)
}
// Create accepts a CreateOpts struct and creates a new CDN service using the
// values provided.
func Create(c *gophercloud.ServiceClient, opts os.CreateOptsBuilder) os.CreateResult {
return os.Create(c, opts)
}
// Get retrieves a specific service based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) os.GetResult {
return os.Get(c, id)
}
// Update accepts a UpdateOpts struct and updates an existing CDN service using
// the values provided.
func Update(c *gophercloud.ServiceClient, id string, patches []os.Patch) os.UpdateResult {
return os.Update(c, id, patches)
}
// Delete accepts a unique ID and deletes the CDN service associated with it.
func Delete(c *gophercloud.ServiceClient, id string) os.DeleteResult {
return os.Delete(c, id)
}

View File

@@ -1,7 +0,0 @@
// Package services provides information and interaction with the services API
// resource in the Rackspace CDN service. This API resource allows for
// listing, creating, updating, retrieving, and deleting services.
//
// A service represents an application that has its content cached to the edge
// nodes.
package services

View File

@@ -1,12 +0,0 @@
package bootfromvolume
import (
"github.com/rackspace/gophercloud"
osBFV "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
osServers "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
)
// Create requests the creation of a server from the given block device mapping.
func Create(client *gophercloud.ServiceClient, opts osServers.CreateOptsBuilder) osServers.CreateResult {
return osBFV.Create(client, opts)
}

View File

@@ -1,43 +0,0 @@
package flavors
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
"github.com/rackspace/gophercloud/pagination"
)
// ListOpts helps control the results returned by the List() function. For example, a flavor with a
// minDisk field of 10 will not be returned if you specify MinDisk set to 20.
type ListOpts struct {
// MinDisk and MinRAM, if provided, elide flavors that do not meet your criteria.
MinDisk int `q:"minDisk"`
MinRAM int `q:"minRam"`
// Marker specifies the ID of the last flavor in the previous page.
Marker string `q:"marker"`
// Limit instructs List to refrain from sending excessively large lists of flavors.
Limit int `q:"limit"`
}
// ToFlavorListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToFlavorListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// ListDetail enumerates the server images available to your account.
func ListDetail(client *gophercloud.ServiceClient, opts os.ListOptsBuilder) pagination.Pager {
return os.ListDetail(client, opts)
}
// Get returns details about a single flavor, identity by ID.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
return res
}

View File

@@ -1,3 +0,0 @@
// Package flavors provides information and interaction with the flavor
// API resource for the Rackspace Cloud Servers service.
package flavors

View File

@@ -1,137 +0,0 @@
// +build fixtures
package flavors
// ListOutput is a sample response of a flavor List request.
const ListOutput = `
{
"flavors": [
{
"OS-FLV-EXT-DATA:ephemeral": 0,
"OS-FLV-WITH-EXT-SPECS:extra_specs": {
"class": "performance1",
"disk_io_index": "40",
"number_of_data_disks": "0",
"policy_class": "performance_flavor",
"resize_policy_class": "performance_flavor"
},
"disk": 20,
"id": "performance1-1",
"links": [
{
"href": "https://iad.servers.api.rackspacecloud.com/v2/864477/flavors/performance1-1",
"rel": "self"
},
{
"href": "https://iad.servers.api.rackspacecloud.com/864477/flavors/performance1-1",
"rel": "bookmark"
}
],
"name": "1 GB Performance",
"ram": 1024,
"rxtx_factor": 200,
"swap": "",
"vcpus": 1
},
{
"OS-FLV-EXT-DATA:ephemeral": 20,
"OS-FLV-WITH-EXT-SPECS:extra_specs": {
"class": "performance1",
"disk_io_index": "40",
"number_of_data_disks": "1",
"policy_class": "performance_flavor",
"resize_policy_class": "performance_flavor"
},
"disk": 40,
"id": "performance1-2",
"links": [
{
"href": "https://iad.servers.api.rackspacecloud.com/v2/864477/flavors/performance1-2",
"rel": "self"
},
{
"href": "https://iad.servers.api.rackspacecloud.com/864477/flavors/performance1-2",
"rel": "bookmark"
}
],
"name": "2 GB Performance",
"ram": 2048,
"rxtx_factor": 400,
"swap": "",
"vcpus": 2
}
]
}`
// GetOutput is a sample response from a flavor Get request. Its contents correspond to the
// Performance1Flavor struct.
const GetOutput = `
{
"flavor": {
"OS-FLV-EXT-DATA:ephemeral": 0,
"OS-FLV-WITH-EXT-SPECS:extra_specs": {
"class": "performance1",
"disk_io_index": "40",
"number_of_data_disks": "0",
"policy_class": "performance_flavor",
"resize_policy_class": "performance_flavor"
},
"disk": 20,
"id": "performance1-1",
"links": [
{
"href": "https://iad.servers.api.rackspacecloud.com/v2/864477/flavors/performance1-1",
"rel": "self"
},
{
"href": "https://iad.servers.api.rackspacecloud.com/864477/flavors/performance1-1",
"rel": "bookmark"
}
],
"name": "1 GB Performance",
"ram": 1024,
"rxtx_factor": 200,
"swap": "",
"vcpus": 1
}
}
`
// Performance1Flavor is the expected result of parsing GetOutput, or the first element of
// ListOutput.
var Performance1Flavor = Flavor{
ID: "performance1-1",
Disk: 20,
RAM: 1024,
Name: "1 GB Performance",
RxTxFactor: 200.0,
Swap: 0,
VCPUs: 1,
ExtraSpecs: ExtraSpecs{
NumDataDisks: 0,
Class: "performance1",
DiskIOIndex: 0,
PolicyClass: "performance_flavor",
},
}
// Performance2Flavor is the second result expected from parsing ListOutput.
var Performance2Flavor = Flavor{
ID: "performance1-2",
Disk: 40,
RAM: 2048,
Name: "2 GB Performance",
RxTxFactor: 400.0,
Swap: 0,
VCPUs: 2,
ExtraSpecs: ExtraSpecs{
NumDataDisks: 0,
Class: "performance1",
DiskIOIndex: 0,
PolicyClass: "performance_flavor",
},
}
// ExpectedFlavorSlice is the slice of Flavor structs that are expected to be parsed from
// ListOutput.
var ExpectedFlavorSlice = []Flavor{Performance1Flavor, Performance2Flavor}

View File

@@ -1,104 +0,0 @@
package flavors
import (
"reflect"
"github.com/rackspace/gophercloud"
"github.com/mitchellh/mapstructure"
os "github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
"github.com/rackspace/gophercloud/pagination"
)
// ExtraSpecs provide additional information about the flavor.
type ExtraSpecs struct {
// The number of data disks
NumDataDisks int `mapstructure:"number_of_data_disks"`
// The flavor class
Class string `mapstructure:"class"`
// Relative measure of disk I/O performance from 0-99, where higher is faster
DiskIOIndex int `mapstructure:"disk_io_index"`
PolicyClass string `mapstructure:"policy_class"`
}
// Flavor records represent (virtual) hardware configurations for server resources in a region.
type Flavor struct {
// The Id field contains the flavor's unique identifier.
// For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance.
ID string `mapstructure:"id"`
// The Disk and RA< fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
Disk int `mapstructure:"disk"`
RAM int `mapstructure:"ram"`
// The Name field provides a human-readable moniker for the flavor.
Name string `mapstructure:"name"`
RxTxFactor float64 `mapstructure:"rxtx_factor"`
// Swap indicates how much space is reserved for swap.
// If not provided, this field will be set to 0.
Swap int `mapstructure:"swap"`
// VCPUs indicates how many (virtual) CPUs are available for this flavor.
VCPUs int `mapstructure:"vcpus"`
// ExtraSpecs provides extra information about the flavor
ExtraSpecs ExtraSpecs `mapstructure:"OS-FLV-WITH-EXT-SPECS:extra_specs"`
}
// GetResult temporarily holds the response from a Get call.
type GetResult struct {
gophercloud.Result
}
// Extract provides access to the individual Flavor returned by the Get function.
func (gr GetResult) Extract() (*Flavor, error) {
if gr.Err != nil {
return nil, gr.Err
}
var result struct {
Flavor Flavor `mapstructure:"flavor"`
}
cfg := &mapstructure.DecoderConfig{
DecodeHook: defaulter,
Result: &result,
}
decoder, err := mapstructure.NewDecoder(cfg)
if err != nil {
return nil, err
}
err = decoder.Decode(gr.Body)
return &result.Flavor, err
}
func defaulter(from, to reflect.Kind, v interface{}) (interface{}, error) {
if (from == reflect.String) && (to == reflect.Int) {
return 0, nil
}
return v, nil
}
// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation.
func ExtractFlavors(page pagination.Page) ([]Flavor, error) {
casted := page.(os.FlavorPage).Body
var container struct {
Flavors []Flavor `mapstructure:"flavors"`
}
cfg := &mapstructure.DecoderConfig{
DecodeHook: defaulter,
Result: &container,
}
decoder, err := mapstructure.NewDecoder(cfg)
if err != nil {
return container.Flavors, err
}
err = decoder.Decode(casted)
if err != nil {
return container.Flavors, err
}
return container.Flavors, nil
}

View File

@@ -1,9 +0,0 @@
package flavors
import (
"github.com/rackspace/gophercloud"
)
func getURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id)
}

View File

@@ -1,22 +0,0 @@
package images
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/compute/v2/images"
"github.com/rackspace/gophercloud/pagination"
)
// ListDetail enumerates the available server images.
func ListDetail(client *gophercloud.ServiceClient, opts os.ListOptsBuilder) pagination.Pager {
return os.ListDetail(client, opts)
}
// Get acquires additional detail about a specific image by ID.
func Get(client *gophercloud.ServiceClient, id string) os.GetResult {
return os.Get(client, id)
}
// ExtractImages interprets a page as a collection of server images.
func ExtractImages(page pagination.Page) ([]os.Image, error) {
return os.ExtractImages(page)
}

View File

@@ -1,3 +0,0 @@
// Package images provides information and interaction with the image
// API resource for the Rackspace Cloud Servers service.
package images

View File

@@ -1,200 +0,0 @@
// +build fixtures
package images
import (
os "github.com/rackspace/gophercloud/openstack/compute/v2/images"
)
// ListOutput is an example response from an /images/detail request.
const ListOutput = `
{
"images": [
{
"OS-DCF:diskConfig": "MANUAL",
"OS-EXT-IMG-SIZE:size": 1.017415075e+09,
"created": "2014-10-01T15:49:02Z",
"id": "30aa010e-080e-4d4b-a7f9-09fc55b07d69",
"links": [
{
"href": "https://iad.servers.api.rackspacecloud.com/v2/111222/images/30aa010e-080e-4d4b-a7f9-09fc55b07d69",
"rel": "self"
},
{
"href": "https://iad.servers.api.rackspacecloud.com/111222/images/30aa010e-080e-4d4b-a7f9-09fc55b07d69",
"rel": "bookmark"
},
{
"href": "https://iad.servers.api.rackspacecloud.com/111222/images/30aa010e-080e-4d4b-a7f9-09fc55b07d69",
"rel": "alternate",
"type": "application/vnd.openstack.image"
}
],
"metadata": {
"auto_disk_config": "disabled",
"cache_in_nova": "True",
"com.rackspace__1__build_core": "1",
"com.rackspace__1__build_managed": "1",
"com.rackspace__1__build_rackconnect": "1",
"com.rackspace__1__options": "0",
"com.rackspace__1__platform_target": "PublicCloud",
"com.rackspace__1__release_build_date": "2014-10-01_15-46-08",
"com.rackspace__1__release_id": "100",
"com.rackspace__1__release_version": "10",
"com.rackspace__1__source": "kickstart",
"com.rackspace__1__visible_core": "1",
"com.rackspace__1__visible_managed": "0",
"com.rackspace__1__visible_rackconnect": "0",
"image_type": "base",
"org.openstack__1__architecture": "x64",
"org.openstack__1__os_distro": "org.archlinux",
"org.openstack__1__os_version": "2014.8",
"os_distro": "arch",
"os_type": "linux",
"vm_mode": "hvm"
},
"minDisk": 20,
"minRam": 512,
"name": "Arch 2014.10 (PVHVM)",
"progress": 100,
"status": "ACTIVE",
"updated": "2014-10-01T19:37:58Z"
},
{
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-IMG-SIZE:size": 1.060306463e+09,
"created": "2014-10-01T12:58:11Z",
"id": "e19a734c-c7e6-443a-830c-242209c4d65d",
"links": [
{
"href": "https://iad.servers.api.rackspacecloud.com/v2/111222/images/e19a734c-c7e6-443a-830c-242209c4d65d",
"rel": "self"
},
{
"href": "https://iad.servers.api.rackspacecloud.com/111222/images/e19a734c-c7e6-443a-830c-242209c4d65d",
"rel": "bookmark"
},
{
"href": "https://iad.servers.api.rackspacecloud.com/111222/images/e19a734c-c7e6-443a-830c-242209c4d65d",
"rel": "alternate",
"type": "application/vnd.openstack.image"
}
],
"metadata": {
"auto_disk_config": "True",
"cache_in_nova": "True",
"com.rackspace__1__build_core": "1",
"com.rackspace__1__build_managed": "1",
"com.rackspace__1__build_rackconnect": "1",
"com.rackspace__1__options": "0",
"com.rackspace__1__platform_target": "PublicCloud",
"com.rackspace__1__release_build_date": "2014-10-01_12-31-03",
"com.rackspace__1__release_id": "1007",
"com.rackspace__1__release_version": "6",
"com.rackspace__1__source": "kickstart",
"com.rackspace__1__visible_core": "1",
"com.rackspace__1__visible_managed": "1",
"com.rackspace__1__visible_rackconnect": "1",
"image_type": "base",
"org.openstack__1__architecture": "x64",
"org.openstack__1__os_distro": "com.ubuntu",
"org.openstack__1__os_version": "14.04",
"os_distro": "ubuntu",
"os_type": "linux",
"vm_mode": "xen"
},
"minDisk": 20,
"minRam": 512,
"name": "Ubuntu 14.04 LTS (Trusty Tahr)",
"progress": 100,
"status": "ACTIVE",
"updated": "2014-10-01T15:51:44Z"
}
]
}
`
// GetOutput is an example response from an /images request.
const GetOutput = `
{
"image": {
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-IMG-SIZE:size": 1060306463,
"created": "2014-10-01T12:58:11Z",
"id": "e19a734c-c7e6-443a-830c-242209c4d65d",
"links": [
{
"href": "https://iad.servers.api.rackspacecloud.com/v2/111222/images/e19a734c-c7e6-443a-830c-242209c4d65d",
"rel": "self"
},
{
"href": "https://iad.servers.api.rackspacecloud.com/111222/images/e19a734c-c7e6-443a-830c-242209c4d65d",
"rel": "bookmark"
},
{
"href": "https://iad.servers.api.rackspacecloud.com/111222/images/e19a734c-c7e6-443a-830c-242209c4d65d",
"rel": "alternate",
"type": "application/vnd.openstack.image"
}
],
"metadata": {
"auto_disk_config": "True",
"cache_in_nova": "True",
"com.rackspace__1__build_core": "1",
"com.rackspace__1__build_managed": "1",
"com.rackspace__1__build_rackconnect": "1",
"com.rackspace__1__options": "0",
"com.rackspace__1__platform_target": "PublicCloud",
"com.rackspace__1__release_build_date": "2014-10-01_12-31-03",
"com.rackspace__1__release_id": "1007",
"com.rackspace__1__release_version": "6",
"com.rackspace__1__source": "kickstart",
"com.rackspace__1__visible_core": "1",
"com.rackspace__1__visible_managed": "1",
"com.rackspace__1__visible_rackconnect": "1",
"image_type": "base",
"org.openstack__1__architecture": "x64",
"org.openstack__1__os_distro": "com.ubuntu",
"org.openstack__1__os_version": "14.04",
"os_distro": "ubuntu",
"os_type": "linux",
"vm_mode": "xen"
},
"minDisk": 20,
"minRam": 512,
"name": "Ubuntu 14.04 LTS (Trusty Tahr)",
"progress": 100,
"status": "ACTIVE",
"updated": "2014-10-01T15:51:44Z"
}
}
`
// ArchImage is the first Image structure that should be parsed from ListOutput.
var ArchImage = os.Image{
ID: "30aa010e-080e-4d4b-a7f9-09fc55b07d69",
Name: "Arch 2014.10 (PVHVM)",
Created: "2014-10-01T15:49:02Z",
Updated: "2014-10-01T19:37:58Z",
MinDisk: 20,
MinRAM: 512,
Progress: 100,
Status: "ACTIVE",
}
// UbuntuImage is the second Image structure that should be parsed from ListOutput and
// the only image that should be extracted from GetOutput.
var UbuntuImage = os.Image{
ID: "e19a734c-c7e6-443a-830c-242209c4d65d",
Name: "Ubuntu 14.04 LTS (Trusty Tahr)",
Created: "2014-10-01T12:58:11Z",
Updated: "2014-10-01T15:51:44Z",
MinDisk: 20,
MinRAM: 512,
Progress: 100,
Status: "ACTIVE",
}
// ExpectedImageSlice is the collection of images that should be parsed from ListOutput,
// in order.
var ExpectedImageSlice = []os.Image{ArchImage, UbuntuImage}

View File

@@ -1,33 +0,0 @@
package keypairs
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
"github.com/rackspace/gophercloud/pagination"
)
// List returns a Pager that allows you to iterate over a collection of KeyPairs.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return os.List(client)
}
// Create requests the creation of a new keypair on the server, or to import a pre-existing
// keypair.
func Create(client *gophercloud.ServiceClient, opts os.CreateOptsBuilder) os.CreateResult {
return os.Create(client, opts)
}
// Get returns public data about a previously uploaded KeyPair.
func Get(client *gophercloud.ServiceClient, name string) os.GetResult {
return os.Get(client, name)
}
// Delete requests the deletion of a previous stored KeyPair from the server.
func Delete(client *gophercloud.ServiceClient, name string) os.DeleteResult {
return os.Delete(client, name)
}
// ExtractKeyPairs interprets a page of results as a slice of KeyPairs.
func ExtractKeyPairs(page pagination.Page) ([]os.KeyPair, error) {
return os.ExtractKeyPairs(page)
}

View File

@@ -1,3 +0,0 @@
// Package keypairs provides information and interaction with the keypair
// API resource for the Rackspace Cloud Servers service.
package keypairs

View File

@@ -1,3 +0,0 @@
// Package networks provides information and interaction with the network
// API resource for the Rackspace Cloud Servers service.
package networks

View File

@@ -1,89 +0,0 @@
package networks
import (
"errors"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// List returns a Pager which allows you to iterate over a collection of
// networks. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
func List(c *gophercloud.ServiceClient) pagination.Pager {
createPage := func(r pagination.PageResult) pagination.Page {
return NetworkPage{pagination.SinglePageBase(r)}
}
return pagination.NewPager(c, listURL(c), createPage)
}
// Get retrieves a specific network 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 {
ToNetworkCreateMap() (map[string]interface{}, error)
}
// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts struct {
// REQUIRED. See Network object for more info.
CIDR string
// REQUIRED. See Network object for more info.
Label string
}
// ToNetworkCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToNetworkCreateMap() (map[string]interface{}, error) {
n := make(map[string]interface{})
if opts.CIDR == "" {
return nil, errors.New("Required field CIDR not set.")
}
if opts.Label == "" {
return nil, errors.New("Required field Label not set.")
}
n["label"] = opts.Label
n["cidr"] = opts.CIDR
return map[string]interface{}{"network": n}, nil
}
// Create accepts a CreateOpts struct and creates a new network using the values
// provided. This operation does not actually require a request body, i.e. the
// CreateOpts struct argument can be empty.
//
// The tenant ID that is contained in the URI is the tenant that creates the
// network. An admin user, however, has the option of specifying another tenant
// ID in the CreateOpts struct.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToNetworkCreateMap()
if err != nil {
res.Err = err
return res
}
// Send request to API
_, res.Err = c.Post(createURL(c), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202},
})
return res
}
// Delete accepts a unique ID and deletes the network associated with it.
func Delete(c *gophercloud.ServiceClient, networkID string) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(deleteURL(c, networkID), nil)
return res
}

View File

@@ -1,81 +0,0 @@
package networks
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 network resource.
func (r commonResult) Extract() (*Network, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
Network *Network `json:"network"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Network, 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
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}
// Network represents, well, a network.
type Network struct {
// UUID for the network
ID string `mapstructure:"id" json:"id"`
// Human-readable name for the network. Might not be unique.
Label string `mapstructure:"label" json:"label"`
// Classless Inter-Domain Routing
CIDR string `mapstructure:"cidr" json:"cidr"`
}
// NetworkPage is the page returned by a pager when traversing over a
// collection of networks.
type NetworkPage struct {
pagination.SinglePageBase
}
// IsEmpty returns true if the NetworkPage contains no Networks.
func (r NetworkPage) IsEmpty() (bool, error) {
networks, err := ExtractNetworks(r)
if err != nil {
return true, err
}
return len(networks) == 0, nil
}
// ExtractNetworks accepts a Page struct, specifically a NetworkPage struct,
// and extracts the elements into a slice of Network structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractNetworks(page pagination.Page) ([]Network, error) {
var resp struct {
Networks []Network `mapstructure:"networks" json:"networks"`
}
err := mapstructure.Decode(page.(NetworkPage).Body, &resp)
return resp.Networks, err
}

View File

@@ -1,27 +0,0 @@
package networks
import "github.com/rackspace/gophercloud"
func resourceURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("os-networksv2", id)
}
func rootURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("os-networksv2")
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return resourceURL(c, id)
}
func listURL(c *gophercloud.ServiceClient) string {
return rootURL(c)
}
func createURL(c *gophercloud.ServiceClient) string {
return rootURL(c)
}
func deleteURL(c *gophercloud.ServiceClient, id string) string {
return resourceURL(c, id)
}

View File

@@ -1,45 +0,0 @@
package virtualinterfaces
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// List returns a Pager which allows you to iterate over a collection of
// networks. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
func List(c *gophercloud.ServiceClient, instanceID string) pagination.Pager {
createPage := func(r pagination.PageResult) pagination.Page {
return VirtualInterfacePage{pagination.SinglePageBase(r)}
}
return pagination.NewPager(c, listURL(c, instanceID), createPage)
}
// Create creates a new virtual interface for a network and attaches the network
// to the server instance.
func Create(c *gophercloud.ServiceClient, instanceID, networkID string) CreateResult {
var res CreateResult
reqBody := map[string]map[string]string{
"virtual_interface": {
"network_id": networkID,
},
}
// Send request to API
_, res.Err = c.Post(createURL(c, instanceID), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202},
})
return res
}
// Delete deletes the interface with interfaceID attached to the instance with
// instanceID.
func Delete(c *gophercloud.ServiceClient, instanceID, interfaceID string) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(deleteURL(c, instanceID, interfaceID), &gophercloud.RequestOpts{
OkCodes: []int{200, 204},
})
return res
}

View File

@@ -1,81 +0,0 @@
package virtualinterfaces
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 network resource.
func (r commonResult) Extract() (*VirtualInterface, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
VirtualInterfaces []VirtualInterface `mapstructure:"virtual_interfaces" json:"virtual_interfaces"`
}
err := mapstructure.Decode(r.Body, &res)
return &res.VirtualInterfaces[0], err
}
// CreateResult represents the result of a create operation.
type CreateResult struct {
commonResult
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}
// IPAddress represents a vitual address attached to a VirtualInterface.
type IPAddress struct {
Address string `mapstructure:"address" json:"address"`
NetworkID string `mapstructure:"network_id" json:"network_id"`
NetworkLabel string `mapstructure:"network_label" json:"network_label"`
}
// VirtualInterface represents a virtual interface.
type VirtualInterface struct {
// UUID for the virtual interface
ID string `mapstructure:"id" json:"id"`
MACAddress string `mapstructure:"mac_address" json:"mac_address"`
IPAddresses []IPAddress `mapstructure:"ip_addresses" json:"ip_addresses"`
}
// VirtualInterfacePage is the page returned by a pager when traversing over a
// collection of virtual interfaces.
type VirtualInterfacePage struct {
pagination.SinglePageBase
}
// IsEmpty returns true if the NetworkPage contains no Networks.
func (r VirtualInterfacePage) IsEmpty() (bool, error) {
networks, err := ExtractVirtualInterfaces(r)
if err != nil {
return true, err
}
return len(networks) == 0, nil
}
// ExtractVirtualInterfaces accepts a Page struct, specifically a VirtualInterfacePage struct,
// and extracts the elements into a slice of VirtualInterface structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractVirtualInterfaces(page pagination.Page) ([]VirtualInterface, error) {
var resp struct {
VirtualInterfaces []VirtualInterface `mapstructure:"virtual_interfaces" json:"virtual_interfaces"`
}
err := mapstructure.Decode(page.(VirtualInterfacePage).Body, &resp)
return resp.VirtualInterfaces, err
}

View File

@@ -1,15 +0,0 @@
package virtualinterfaces
import "github.com/rackspace/gophercloud"
func listURL(c *gophercloud.ServiceClient, instanceID string) string {
return c.ServiceURL("servers", instanceID, "os-virtual-interfacesv2")
}
func createURL(c *gophercloud.ServiceClient, instanceID string) string {
return c.ServiceURL("servers", instanceID, "os-virtual-interfacesv2")
}
func deleteURL(c *gophercloud.ServiceClient, instanceID, interfaceID string) string {
return c.ServiceURL("servers", instanceID, "os-virtual-interfacesv2", interfaceID)
}

View File

@@ -1,6 +0,0 @@
// Package backups provides information and interaction with the backup API
// resource in the Rackspace Database service.
//
// A backup is a copy of a database instance that can be used to restore it to
// some defined point in history.
package backups

View File

@@ -1,66 +0,0 @@
package backups
import "time"
var (
timestamp = "2015-11-12T14:22:42Z"
timeVal, _ = time.Parse(time.RFC3339, timestamp)
)
var getResp = `
{
"backup": {
"created": "` + timestamp + `",
"description": "My Backup",
"id": "61f12fef-edb1-4561-8122-e7c00ef26a82",
"instance_id": "d4603f69-ec7e-4e9b-803f-600b9205576f",
"locationRef": null,
"name": "snapshot",
"parent_id": null,
"size": 100,
"status": "NEW",
"datastore": {
"version": "5.1",
"type": "MySQL",
"version_id": "20000000-0000-0000-0000-000000000002"
},
"updated": "` + timestamp + `"
}
}
`
var createReq = `
{
"backup": {
"description": "My Backup",
"instance": "d4603f69-ec7e-4e9b-803f-600b9205576f",
"name": "snapshot"
}
}
`
var createResp = getResp
var listResp = `
{
"backups": [
{
"status": "COMPLETED",
"updated": "` + timestamp + `",
"description": "Backup from Restored Instance",
"datastore": {
"version": "5.1",
"type": "MySQL",
"version_id": "20000000-0000-0000-0000-000000000002"
},
"id": "87972694-4be2-40f5-83f8-501656e0032a",
"size": 0.141026,
"name": "restored_backup",
"created": "` + timestamp + `",
"instance_id": "29af2cd9-0674-48ab-b87a-b160f00208e6",
"parent_id": null,
"locationRef": "http://localhost/path/to/backup"
}
]
}
`

View File

@@ -1,138 +0,0 @@
package backups
import (
"errors"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// CreateOptsBuilder is the top-level interface for creating JSON maps.
type CreateOptsBuilder interface {
ToBackupCreateMap() (map[string]interface{}, error)
}
// CreateOpts is responsible for configuring newly provisioned backups.
type CreateOpts struct {
// [REQUIRED] The name of the backup. The only restriction is the name must
// be less than 64 characters long.
Name string
// [REQUIRED] The ID of the instance being backed up.
InstanceID string
// [OPTIONAL] A human-readable explanation of the backup.
Description string
}
// ToBackupCreateMap will create a JSON map for the Create operation.
func (opts CreateOpts) ToBackupCreateMap() (map[string]interface{}, error) {
if opts.Name == "" {
return nil, errors.New("Name is a required field")
}
if opts.InstanceID == "" {
return nil, errors.New("InstanceID is a required field")
}
backup := map[string]interface{}{
"name": opts.Name,
"instance": opts.InstanceID,
}
if opts.Description != "" {
backup["description"] = opts.Description
}
return map[string]interface{}{"backup": backup}, nil
}
// Create asynchronously creates a new backup for a specified database instance.
// During the backup process, write access on MyISAM databases will be
// temporarily disabled; innoDB databases will be unaffected. During this time,
// you will not be able to add or delete databases or users; nor delete, stop
// or reboot the instance itself. Only one backup is permitted at once.
//
// Backups are not deleted when database instances are deleted; you must
// manually delete any backups created using Delete(). Backups are saved to your
// Cloud Files account in a new container called z_CLOUDDB_BACKUPS. It is
// strongly recommended you do not alter this container or its contents; usual
// storage costs apply.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToBackupCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Request("POST", baseURL(client), gophercloud.RequestOpts{
JSONBody: &reqBody,
JSONResponse: &res.Body,
OkCodes: []int{202},
})
return res
}
// ListOptsBuilder is the top-level interface for creating query strings.
type ListOptsBuilder interface {
ToBackupListQuery() (string, error)
}
// ListOpts allows you to refine a list search by certain parameters.
type ListOpts struct {
// The type of datastore by which to filter.
Datastore string `q:"datastore"`
}
// ToBackupListQuery converts a ListOpts struct into a query string.
func (opts ListOpts) ToBackupListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// List will list all the saved backups for all database instances.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := baseURL(client)
if opts != nil {
query, err := opts.ToBackupListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
pageFn := func(r pagination.PageResult) pagination.Page {
return BackupPage{pagination.SinglePageBase(r)}
}
return pagination.NewPager(client, url, pageFn)
}
// Get will retrieve details for a particular backup based on its unique ID.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = client.Request("GET", resourceURL(client, id), gophercloud.RequestOpts{
JSONResponse: &res.Body,
OkCodes: []int{200},
})
return res
}
// Delete will permanently delete a backup.
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
var res DeleteResult
_, res.Err = client.Request("DELETE", resourceURL(client, id), gophercloud.RequestOpts{
OkCodes: []int{202},
})
return res
}

View File

@@ -1,149 +0,0 @@
package backups
import (
"fmt"
"reflect"
"time"
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/db/v1/datastores"
"github.com/rackspace/gophercloud/pagination"
)
// Status represents the various states a Backup can be in.
type Status string
// Enum types for the status.
const (
StatusNew Status = "NEW"
StatusBuilding Status = "BUILDING"
StatusCompleted Status = "COMPLETED"
StatusFailed Status = "FAILED"
StatusDeleteFailed Status = "DELETE_FAILED"
)
// Backup represents a Backup API resource.
type Backup struct {
Description string
ID string
InstanceID string `json:"instance_id" mapstructure:"instance_id"`
LocationRef string
Name string
ParentID string `json:"parent_id" mapstructure:"parent_id"`
Size float64
Status Status
Created time.Time `mapstructure:"-"`
Updated time.Time `mapstructure:"-"`
Datastore datastores.DatastorePartial
}
// CreateResult represents the result of a create operation.
type CreateResult struct {
commonResult
}
// GetResult represents the result of a get operation.
type GetResult struct {
commonResult
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}
type commonResult struct {
gophercloud.Result
}
// Extract will retrieve a Backup struct from an operation's result.
func (r commonResult) Extract() (*Backup, error) {
if r.Err != nil {
return nil, r.Err
}
var response struct {
Backup Backup `mapstructure:"backup"`
}
err := mapstructure.Decode(r.Body, &response)
val := r.Body.(map[string]interface{})["backup"].(map[string]interface{})
if t, ok := val["created"].(string); ok && t != "" {
creationTime, err := time.Parse(time.RFC3339, t)
if err != nil {
return &response.Backup, err
}
response.Backup.Created = creationTime
}
if t, ok := val["updated"].(string); ok && t != "" {
updatedTime, err := time.Parse(time.RFC3339, t)
if err != nil {
return &response.Backup, err
}
response.Backup.Updated = updatedTime
}
return &response.Backup, err
}
// BackupPage represents a page of backups.
type BackupPage struct {
pagination.SinglePageBase
}
// IsEmpty checks whether an BackupPage struct is empty.
func (r BackupPage) IsEmpty() (bool, error) {
is, err := ExtractBackups(r)
if err != nil {
return true, err
}
return len(is) == 0, nil
}
// ExtractBackups will retrieve a slice of Backup structs from a paginated collection.
func ExtractBackups(page pagination.Page) ([]Backup, error) {
casted := page.(BackupPage).Body
var resp struct {
Backups []Backup `mapstructure:"backups" json:"backups"`
}
if err := mapstructure.Decode(casted, &resp); err != nil {
return nil, err
}
var vals []interface{}
switch casted.(type) {
case map[string]interface{}:
vals = casted.(map[string]interface{})["backups"].([]interface{})
case map[string][]interface{}:
vals = casted.(map[string][]interface{})["backups"]
default:
return resp.Backups, fmt.Errorf("Unknown type: %v", reflect.TypeOf(casted))
}
for i, v := range vals {
val := v.(map[string]interface{})
if t, ok := val["created"].(string); ok && t != "" {
creationTime, err := time.Parse(time.RFC3339, t)
if err != nil {
return resp.Backups, err
}
resp.Backups[i].Created = creationTime
}
if t, ok := val["updated"].(string); ok && t != "" {
updatedTime, err := time.Parse(time.RFC3339, t)
if err != nil {
return resp.Backups, err
}
resp.Backups[i].Updated = updatedTime
}
}
return resp.Backups, nil
}

View File

@@ -1,11 +0,0 @@
package backups
import "github.com/rackspace/gophercloud"
func baseURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("backups")
}
func resourceURL(c *gophercloud.ServiceClient, backupID string) string {
return c.ServiceURL("backups", backupID)
}

View File

@@ -1,79 +0,0 @@
package configurations
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/db/v1/configurations"
"github.com/rackspace/gophercloud/pagination"
)
// List will list all of the available configurations.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return os.List(client)
}
// Create will create a new configuration group.
func Create(client *gophercloud.ServiceClient, opts os.CreateOptsBuilder) os.CreateResult {
return os.Create(client, opts)
}
// Get will retrieve the details for a specified configuration group.
func Get(client *gophercloud.ServiceClient, configID string) os.GetResult {
return os.Get(client, configID)
}
// Update will modify an existing configuration group by performing a merge
// between new and existing values. If the key already exists, the new value
// will overwrite. All other keys will remain unaffected.
func Update(client *gophercloud.ServiceClient, configID string, opts os.UpdateOptsBuilder) os.UpdateResult {
return os.Update(client, configID, opts)
}
// Replace will modify an existing configuration group by overwriting the
// entire parameter group with the new values provided. Any existing keys not
// included in UpdateOptsBuilder will be deleted.
func Replace(client *gophercloud.ServiceClient, configID string, opts os.UpdateOptsBuilder) os.ReplaceResult {
return os.Replace(client, configID, opts)
}
// Delete will permanently delete a configuration group. Please note that
// config groups cannot be deleted whilst still attached to running instances -
// you must detach and then delete them.
func Delete(client *gophercloud.ServiceClient, configID string) os.DeleteResult {
return os.Delete(client, configID)
}
// ListInstances will list all the instances associated with a particular
// configuration group.
func ListInstances(client *gophercloud.ServiceClient, configID string) pagination.Pager {
return os.ListInstances(client, configID)
}
// ListDatastoreParams will list all the available and supported parameters
// that can be used for a particular datastore ID and a particular version.
// For example, if you are wondering how you can configure a MySQL 5.6 instance,
// you can use this operation (you will need to retrieve the MySQL datastore ID
// by using the datastores API).
func ListDatastoreParams(client *gophercloud.ServiceClient, datastoreID, versionID string) pagination.Pager {
return os.ListDatastoreParams(client, datastoreID, versionID)
}
// GetDatastoreParam will retrieve information about a specific configuration
// parameter. For example, you can use this operation to understand more about
// "innodb_file_per_table" configuration param for MySQL datastores. You will
// need the param's ID first, which can be attained by using the ListDatastoreParams
// operation.
func GetDatastoreParam(client *gophercloud.ServiceClient, datastoreID, versionID, paramID string) os.ParamResult {
return os.GetDatastoreParam(client, datastoreID, versionID, paramID)
}
// ListGlobalParams is similar to ListDatastoreParams but does not require a
// DatastoreID.
func ListGlobalParams(client *gophercloud.ServiceClient, versionID string) pagination.Pager {
return os.ListGlobalParams(client, versionID)
}
// GetGlobalParam is similar to GetDatastoreParam but does not require a
// DatastoreID.
func GetGlobalParam(client *gophercloud.ServiceClient, versionID, paramID string) os.ParamResult {
return os.GetGlobalParam(client, versionID, paramID)
}

View File

@@ -1 +0,0 @@
package configurations

View File

@@ -1,159 +0,0 @@
package configurations
import (
"fmt"
"time"
os "github.com/rackspace/gophercloud/openstack/db/v1/configurations"
)
var (
timestamp = "2015-11-12T14:22:42Z"
timeVal, _ = time.Parse(time.RFC3339, timestamp)
)
var singleConfigJSON = `
{
"created": "` + timestamp + `",
"datastore_name": "mysql",
"datastore_version_id": "b00000b0-00b0-0b00-00b0-000b000000bb",
"datastore_version_name": "5.6",
"description": "example_description",
"id": "005a8bb7-a8df-40ee-b0b7-fc144641abc2",
"name": "example-configuration-name",
"updated": "` + timestamp + `"
}
`
var singleConfigWithValuesJSON = `
{
"created": "` + timestamp + `",
"datastore_name": "mysql",
"datastore_version_id": "b00000b0-00b0-0b00-00b0-000b000000bb",
"datastore_version_name": "5.6",
"description": "example description",
"id": "005a8bb7-a8df-40ee-b0b7-fc144641abc2",
"instance_count": 0,
"name": "example-configuration-name",
"updated": "` + timestamp + `",
"values": {
"collation_server": "latin1_swedish_ci",
"connect_timeout": 120
}
}
`
var (
listConfigsJSON = fmt.Sprintf(`{"configurations": [%s]}`, singleConfigJSON)
getConfigJSON = fmt.Sprintf(`{"configuration": %s}`, singleConfigJSON)
createConfigJSON = fmt.Sprintf(`{"configuration": %s}`, singleConfigWithValuesJSON)
)
var createReq = `
{
"configuration": {
"datastore": {
"type": "a00000a0-00a0-0a00-00a0-000a000000aa",
"version": "b00000b0-00b0-0b00-00b0-000b000000bb"
},
"description": "example description",
"name": "example-configuration-name",
"values": {
"collation_server": "latin1_swedish_ci",
"connect_timeout": 120
}
}
}
`
var updateReq = `
{
"configuration": {
"values": {
"connect_timeout": 300
}
}
}
`
var listInstancesJSON = `
{
"instances": [
{
"id": "d4603f69-ec7e-4e9b-803f-600b9205576f",
"name": "json_rack_instance"
}
]
}
`
var listParamsJSON = `
{
"configuration-parameters": [
{
"max": 1,
"min": 0,
"name": "innodb_file_per_table",
"restart_required": true,
"type": "integer"
},
{
"max": 4294967296,
"min": 0,
"name": "key_buffer_size",
"restart_required": false,
"type": "integer"
},
{
"max": 65535,
"min": 2,
"name": "connect_timeout",
"restart_required": false,
"type": "integer"
},
{
"max": 4294967296,
"min": 0,
"name": "join_buffer_size",
"restart_required": false,
"type": "integer"
}
]
}
`
var getParamJSON = `
{
"max": 1,
"min": 0,
"name": "innodb_file_per_table",
"restart_required": true,
"type": "integer"
}
`
var exampleConfig = os.Config{
Created: timeVal,
DatastoreName: "mysql",
DatastoreVersionID: "b00000b0-00b0-0b00-00b0-000b000000bb",
DatastoreVersionName: "5.6",
Description: "example_description",
ID: "005a8bb7-a8df-40ee-b0b7-fc144641abc2",
Name: "example-configuration-name",
Updated: timeVal,
}
var exampleConfigWithValues = os.Config{
Created: timeVal,
DatastoreName: "mysql",
DatastoreVersionID: "b00000b0-00b0-0b00-00b0-000b000000bb",
DatastoreVersionName: "5.6",
Description: "example description",
ID: "005a8bb7-a8df-40ee-b0b7-fc144641abc2",
Name: "example-configuration-name",
Updated: timeVal,
Values: map[string]interface{}{
"collation_server": "latin1_swedish_ci",
"connect_timeout": 120,
},
}

View File

@@ -1,19 +0,0 @@
package databases
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/db/v1/databases"
"github.com/rackspace/gophercloud/pagination"
)
func Create(client *gophercloud.ServiceClient, instanceID string, opts os.CreateOptsBuilder) os.CreateResult {
return os.Create(client, instanceID, opts)
}
func List(client *gophercloud.ServiceClient, instanceID string) pagination.Pager {
return os.List(client, instanceID)
}
func Delete(client *gophercloud.ServiceClient, instanceID, dbName string) os.DeleteResult {
return os.Delete(client, instanceID, dbName)
}

View File

@@ -1,3 +0,0 @@
// Package databases provides information and interaction with the database API
// resource in the Rackspace Database service.
package databases

View File

@@ -1 +0,0 @@
package databases

View File

@@ -1,28 +0,0 @@
package datastores
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/db/v1/datastores"
"github.com/rackspace/gophercloud/pagination"
)
// List will list all available flavors.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return os.List(client)
}
// Get retrieves the details for a particular flavor.
func Get(client *gophercloud.ServiceClient, flavorID string) os.GetResult {
return os.Get(client, flavorID)
}
// ListVersions will list all of the available versions for a specified
// datastore type.
func ListVersions(client *gophercloud.ServiceClient, datastoreID string) pagination.Pager {
return os.ListVersions(client, datastoreID)
}
// GetVersion will retrieve the details of a specified datastore version.
func GetVersion(client *gophercloud.ServiceClient, datastoreID, versionID string) os.GetVersionResult {
return os.GetVersion(client, datastoreID, versionID)
}

View File

@@ -1 +0,0 @@
package datastores

View File

@@ -1,17 +0,0 @@
package flavors
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/db/v1/flavors"
"github.com/rackspace/gophercloud/pagination"
)
// List will list all available flavors.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return os.List(client)
}
// Get retrieves the details for a particular flavor.
func Get(client *gophercloud.ServiceClient, flavorID string) os.GetResult {
return os.Get(client, flavorID)
}

View File

@@ -1,3 +0,0 @@
// Package flavors provides information and interaction with the flavor API
// resource in the Rackspace Database service.
package flavors

View File

@@ -1,49 +0,0 @@
package instances
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/db/v1/instances"
)
// Get retrieves the status and information for a specified database instance.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
return GetResult{os.Get(client, id)}
}
// Delete permanently destroys the database instance.
func Delete(client *gophercloud.ServiceClient, id string) os.DeleteResult {
return os.Delete(client, id)
}
// EnableRootUser enables the login from any host for the root user and
// provides the user with a generated root password.
func EnableRootUser(client *gophercloud.ServiceClient, id string) os.UserRootResult {
return os.EnableRootUser(client, id)
}
// IsRootEnabled checks an instance to see if root access is enabled. It returns
// True if root user is enabled for the specified database instance or False
// otherwise.
func IsRootEnabled(client *gophercloud.ServiceClient, id string) (bool, error) {
return os.IsRootEnabled(client, id)
}
// Restart will restart only the MySQL Instance. Restarting MySQL will
// erase any dynamic configuration settings that you have made within MySQL.
// The MySQL service will be unavailable until the instance restarts.
func Restart(client *gophercloud.ServiceClient, id string) os.ActionResult {
return os.Restart(client, id)
}
// Resize changes the memory size of the instance, assuming a valid
// flavorRef is provided. It will also restart the MySQL service.
func Resize(client *gophercloud.ServiceClient, id, flavorRef string) os.ActionResult {
return os.Resize(client, id, flavorRef)
}
// ResizeVolume will resize the attached volume for an instance. It supports
// only increasing the volume size and does not support decreasing the size.
// The volume size is in gigabytes (GB) and must be an integer.
func ResizeVolume(client *gophercloud.ServiceClient, id string, size int) os.ActionResult {
return os.ResizeVolume(client, id, size)
}

View File

@@ -1,3 +0,0 @@
// Package instances provides information and interaction with the instance API
// resource in the Rackspace Database service.
package instances

View File

@@ -1,340 +0,0 @@
package instances
import (
"fmt"
"time"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/db/v1/datastores"
"github.com/rackspace/gophercloud/openstack/db/v1/flavors"
os "github.com/rackspace/gophercloud/openstack/db/v1/instances"
)
var (
timestamp = "2015-11-12T14:22:42Z"
timeVal, _ = time.Parse(time.RFC3339, timestamp)
)
var instance = `
{
"created": "` + timestamp + `",
"datastore": {
"type": "mysql",
"version": "5.6"
},
"flavor": {
"id": "1",
"links": [
{
"href": "https://ord.databases.api.rackspacecloud.com/v1.0/1234/flavors/1",
"rel": "self"
},
{
"href": "https://ord.databases.api.rackspacecloud.com/v1.0/1234/flavors/1",
"rel": "bookmark"
}
]
},
"links": [
{
"href": "https://ord.databases.api.rackspacecloud.com/v1.0/1234/flavors/1",
"rel": "self"
}
],
"hostname": "e09ad9a3f73309469cf1f43d11e79549caf9acf2.rackspaceclouddb.com",
"id": "{instanceID}",
"name": "json_rack_instance",
"status": "BUILD",
"updated": "` + timestamp + `",
"volume": {
"size": 2
}
}
`
var createReq = `
{
"instance": {
"databases": [
{
"character_set": "utf8",
"collate": "utf8_general_ci",
"name": "sampledb"
},
{
"name": "nextround"
}
],
"flavorRef": "1",
"name": "json_rack_instance",
"users": [
{
"databases": [
{
"name": "sampledb"
}
],
"name": "demouser",
"password": "demopassword"
}
],
"volume": {
"size": 2
},
"restorePoint": {
"backupRef": "1234567890"
}
}
}
`
var createReplicaReq = `
{
"instance": {
"volume": {
"size": 1
},
"flavorRef": "9",
"name": "t2s1_ALT_GUEST",
"replica_of": "6bdca2fc-418e-40bd-a595-62abda61862d"
}
}
`
var createReplicaResp = `
{
"instance": {
"status": "BUILD",
"updated": "` + timestamp + `",
"name": "t2s1_ALT_GUEST",
"links": [
{
"href": "https://ord.databases.api.rackspacecloud.com/v1.0/5919009/instances/8367c312-7c40-4a66-aab1-5767478914fc",
"rel": "self"
},
{
"href": "https://ord.databases.api.rackspacecloud.com/instances/8367c312-7c40-4a66-aab1-5767478914fc",
"rel": "bookmark"
}
],
"created": "` + timestamp + `",
"id": "8367c312-7c40-4a66-aab1-5767478914fc",
"volume": {
"size": 1
},
"flavor": {
"id": "9"
},
"datastore": {
"version": "5.6",
"type": "mysql"
},
"replica_of": {
"id": "6bdca2fc-418e-40bd-a595-62abda61862d"
}
}
}
`
var listReplicasResp = `
{
"instances": [
{
"status": "ACTIVE",
"name": "t1s1_ALT_GUEST",
"links": [
{
"href": "https://ord.databases.api.rackspacecloud.com/v1.0/1234/instances/3c691f06-bf9a-4618-b7ec-2817ce0cf254",
"rel": "self"
},
{
"href": "https://ord.databases.api.rackspacecloud.com/instances/3c691f06-bf9a-4618-b7ec-2817ce0cf254",
"rel": "bookmark"
}
],
"ip": [
"10.0.0.3"
],
"id": "3c691f06-bf9a-4618-b7ec-2817ce0cf254",
"volume": {
"size": 1
},
"flavor": {
"id": "9"
},
"datastore": {
"version": "5.6",
"type": "mysql"
},
"replica_of": {
"id": "8b499b45-52d6-402d-b398-f9d8f279c69a"
}
}
]
}
`
var getReplicaResp = `
{
"instance": {
"status": "ACTIVE",
"updated": "` + timestamp + `",
"name": "t1_ALT_GUEST",
"created": "` + timestamp + `",
"ip": [
"10.0.0.2"
],
"replicas": [
{
"id": "3c691f06-bf9a-4618-b7ec-2817ce0cf254"
}
],
"id": "8b499b45-52d6-402d-b398-f9d8f279c69a",
"volume": {
"used": 0.54,
"size": 1
},
"flavor": {
"id": "9"
},
"datastore": {
"version": "5.6",
"type": "mysql"
}
}
}
`
var detachReq = `
{
"instance": {
"replica_of": "",
"slave_of": ""
}
}
`
var getConfigResp = `
{
"instance": {
"configuration": {
"basedir": "/usr",
"connect_timeout": "15",
"datadir": "/var/lib/mysql",
"default_storage_engine": "innodb",
"innodb_buffer_pool_instances": "1",
"innodb_buffer_pool_size": "175M",
"innodb_checksum_algorithm": "crc32",
"innodb_data_file_path": "ibdata1:10M:autoextend",
"innodb_file_per_table": "1",
"innodb_io_capacity": "200",
"innodb_log_file_size": "256M",
"innodb_log_files_in_group": "2",
"innodb_open_files": "8192",
"innodb_thread_concurrency": "0",
"join_buffer_size": "1M",
"key_buffer_size": "50M",
"local-infile": "0",
"log-error": "/var/log/mysql/mysqld.log",
"max_allowed_packet": "16M",
"max_connect_errors": "10000",
"max_connections": "40",
"max_heap_table_size": "16M",
"myisam-recover": "BACKUP",
"open_files_limit": "8192",
"performance_schema": "off",
"pid_file": "/var/run/mysqld/mysqld.pid",
"port": "3306",
"query_cache_limit": "1M",
"query_cache_size": "8M",
"query_cache_type": "1",
"read_buffer_size": "256K",
"read_rnd_buffer_size": "1M",
"server_id": "1",
"skip-external-locking": "1",
"skip_name_resolve": "1",
"sort_buffer_size": "256K",
"table_open_cache": "4096",
"thread_stack": "192K",
"tmp_table_size": "16M",
"tmpdir": "/var/tmp",
"user": "mysql",
"wait_timeout": "3600"
}
}
}
`
var associateReq = `{"instance": {"configuration": "{configGroupID}"}}`
var listBackupsResp = `
{
"backups": [
{
"status": "COMPLETED",
"updated": "` + timestamp + `",
"description": "Backup from Restored Instance",
"datastore": {
"version": "5.1",
"type": "MySQL",
"version_id": "20000000-0000-0000-0000-000000000002"
},
"id": "87972694-4be2-40f5-83f8-501656e0032a",
"size": 0.141026,
"name": "restored_backup",
"created": "` + timestamp + `",
"instance_id": "29af2cd9-0674-48ab-b87a-b160f00208e6",
"parent_id": null,
"locationRef": "http://localhost/path/to/backup"
}
]
}
`
var (
createResp = fmt.Sprintf(`{"instance":%s}`, instance)
getResp = fmt.Sprintf(`{"instance":%s}`, instance)
associateResp = fmt.Sprintf(`{"instance":%s}`, instance)
listInstancesResp = fmt.Sprintf(`{"instances":[%s]}`, instance)
)
var instanceID = "{instanceID}"
var expectedInstance = &Instance{
Created: timeVal,
Updated: timeVal,
Datastore: datastores.DatastorePartial{Type: "mysql", Version: "5.6"},
Flavor: flavors.Flavor{
ID: "1",
Links: []gophercloud.Link{
gophercloud.Link{Href: "https://ord.databases.api.rackspacecloud.com/v1.0/1234/flavors/1", Rel: "self"},
gophercloud.Link{Href: "https://ord.databases.api.rackspacecloud.com/v1.0/1234/flavors/1", Rel: "bookmark"},
},
},
Hostname: "e09ad9a3f73309469cf1f43d11e79549caf9acf2.rackspaceclouddb.com",
ID: instanceID,
Links: []gophercloud.Link{
gophercloud.Link{Href: "https://ord.databases.api.rackspacecloud.com/v1.0/1234/flavors/1", Rel: "self"},
},
Name: "json_rack_instance",
Status: "BUILD",
Volume: os.Volume{Size: 2},
}
var expectedReplica = &Instance{
Status: "BUILD",
Updated: timeVal,
Name: "t2s1_ALT_GUEST",
Links: []gophercloud.Link{
gophercloud.Link{Rel: "self", Href: "https://ord.databases.api.rackspacecloud.com/v1.0/5919009/instances/8367c312-7c40-4a66-aab1-5767478914fc"},
gophercloud.Link{Rel: "bookmark", Href: "https://ord.databases.api.rackspacecloud.com/instances/8367c312-7c40-4a66-aab1-5767478914fc"},
},
Created: timeVal,
ID: "8367c312-7c40-4a66-aab1-5767478914fc",
Volume: os.Volume{Size: 1},
Flavor: flavors.Flavor{ID: "9"},
Datastore: datastores.DatastorePartial{Version: "5.6", Type: "mysql"},
ReplicaOf: &Instance{
ID: "6bdca2fc-418e-40bd-a595-62abda61862d",
},
}

View File

@@ -1,199 +0,0 @@
package instances
import (
"github.com/rackspace/gophercloud"
osDBs "github.com/rackspace/gophercloud/openstack/db/v1/databases"
os "github.com/rackspace/gophercloud/openstack/db/v1/instances"
osUsers "github.com/rackspace/gophercloud/openstack/db/v1/users"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/db/v1/backups"
)
// CreateOpts is the struct responsible for configuring a new database instance.
type CreateOpts struct {
// Either the integer UUID (in string form) of the flavor, or its URI
// reference as specified in the response from the List() call. Required.
FlavorRef string
// Specifies the volume size in gigabytes (GB). The value must be between 1
// and 300. Required.
Size int
// Name of the instance to create. The length of the name is limited to
// 255 characters and any characters are permitted. Optional.
Name string
// A slice of database information options.
Databases osDBs.CreateOptsBuilder
// A slice of user information options.
Users osUsers.CreateOptsBuilder
// ID of the configuration group to associate with the instance. Optional.
ConfigID string
// Options to configure the type of datastore the instance will use. This is
// optional, and if excluded will default to MySQL.
Datastore *os.DatastoreOpts
// Specifies the backup ID from which to restore the database instance. There
// are some things to be aware of before using this field. When you execute
// the Restore Backup operation, a new database instance is created to store
// the backup whose ID is specified by the restorePoint attribute. This will
// mean that:
// - All users, passwords and access that were on the instance at the time of
// the backup will be restored along with the databases.
// - You can create new users or databases if you want, but they cannot be
// the same as the ones from the instance that was backed up.
RestorePoint string
ReplicaOf string
}
func (opts CreateOpts) ToInstanceCreateMap() (map[string]interface{}, error) {
instance, err := os.CreateOpts{
FlavorRef: opts.FlavorRef,
Size: opts.Size,
Name: opts.Name,
Databases: opts.Databases,
Users: opts.Users,
}.ToInstanceCreateMap()
if err != nil {
return nil, err
}
instance = instance["instance"].(map[string]interface{})
if opts.ConfigID != "" {
instance["configuration"] = opts.ConfigID
}
if opts.Datastore != nil {
ds, err := opts.Datastore.ToMap()
if err != nil {
return nil, err
}
instance["datastore"] = ds
}
if opts.RestorePoint != "" {
instance["restorePoint"] = map[string]string{"backupRef": opts.RestorePoint}
}
if opts.ReplicaOf != "" {
instance["replica_of"] = opts.ReplicaOf
}
return map[string]interface{}{"instance": instance}, nil
}
// Create asynchronously provisions a new database instance. It requires the
// user to specify a flavor and a volume size. The API service then provisions
// the instance with the requested flavor and sets up a volume of the specified
// size, which is the storage for the database instance.
//
// Although this call only allows the creation of 1 instance per request, you
// can create an instance with multiple databases and users. The default
// binding for a MySQL instance is port 3306.
func Create(client *gophercloud.ServiceClient, opts os.CreateOptsBuilder) CreateResult {
return CreateResult{os.Create(client, opts)}
}
// ListOpts specifies all of the query options to be used when returning a list
// of database instances.
type ListOpts struct {
// IncludeHA includes or excludes High Availability instances from the result set
IncludeHA bool `q:"include_ha"`
// IncludeReplicas includes or excludes Replica instances from the result set
IncludeReplicas bool `q:"include_replicas"`
}
// ToInstanceListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToInstanceListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// List retrieves the status and information for all database instances.
func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
url := baseURL(client)
if opts != nil {
query, err := opts.ToInstanceListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
createPageFn := func(r pagination.PageResult) pagination.Page {
return os.InstancePage{pagination.LinkedPageBase{PageResult: r}}
}
return pagination.NewPager(client, url, createPageFn)
}
// GetDefaultConfig lists the default configuration settings from the template
// that was applied to the specified instance. In a sense, this is the vanilla
// configuration setting applied to an instance. Further configuration can be
// applied by associating an instance with a configuration group.
func GetDefaultConfig(client *gophercloud.ServiceClient, id string) ConfigResult {
var res ConfigResult
_, res.Err = client.Request("GET", configURL(client, id), gophercloud.RequestOpts{
JSONResponse: &res.Body,
OkCodes: []int{200},
})
return res
}
// AssociateWithConfigGroup associates a specified instance to a specified
// configuration group. If any of the parameters within a configuration group
// require a restart, then the instance will transition into a restart.
func AssociateWithConfigGroup(client *gophercloud.ServiceClient, instanceID, configGroupID string) UpdateResult {
reqBody := map[string]string{
"configuration": configGroupID,
}
var res UpdateResult
_, res.Err = client.Request("PUT", resourceURL(client, instanceID), gophercloud.RequestOpts{
JSONBody: map[string]map[string]string{"instance": reqBody},
OkCodes: []int{202},
})
return res
}
// DetachFromConfigGroup will detach an instance from all config groups.
func DetachFromConfigGroup(client *gophercloud.ServiceClient, instanceID string) UpdateResult {
return AssociateWithConfigGroup(client, instanceID, "")
}
// ListBackups will list all the backups for a specified database instance.
func ListBackups(client *gophercloud.ServiceClient, instanceID string) pagination.Pager {
pageFn := func(r pagination.PageResult) pagination.Page {
return backups.BackupPage{pagination.SinglePageBase(r)}
}
return pagination.NewPager(client, backupsURL(client, instanceID), pageFn)
}
// DetachReplica will detach a specified replica instance from its source
// instance, effectively allowing it to operate independently. Detaching a
// replica will restart the MySQL service on the instance.
func DetachReplica(client *gophercloud.ServiceClient, replicaID string) DetachResult {
var res DetachResult
_, res.Err = client.Request("PATCH", resourceURL(client, replicaID), gophercloud.RequestOpts{
JSONBody: map[string]interface{}{"instance": map[string]string{"replica_of": "", "slave_of": ""}},
OkCodes: []int{202},
})
return res
}

View File

@@ -1,191 +0,0 @@
package instances
import (
"fmt"
"reflect"
"time"
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/db/v1/datastores"
"github.com/rackspace/gophercloud/openstack/db/v1/flavors"
os "github.com/rackspace/gophercloud/openstack/db/v1/instances"
"github.com/rackspace/gophercloud/pagination"
)
// Instance represents a remote MySQL instance.
type Instance struct {
// Indicates the datetime that the instance was created
Created time.Time `mapstructure:"-"`
// Indicates the most recent datetime that the instance was updated.
Updated time.Time `mapstructure:"-"`
// Indicates how the instance stores data.
Datastore datastores.DatastorePartial
// Indicates the hardware flavor the instance uses.
Flavor flavors.Flavor
// A DNS-resolvable hostname associated with the database instance (rather
// than an IPv4 address). Since the hostname always resolves to the correct
// IP address of the database instance, this relieves the user from the task
// of maintaining the mapping. Note that although the IP address may likely
// change on resizing, migrating, and so forth, the hostname always resolves
// to the correct database instance.
Hostname string
// Indicates the unique identifier for the instance resource.
ID string
// Exposes various links that reference the instance resource.
Links []gophercloud.Link
// The human-readable name of the instance.
Name string
// The build status of the instance.
Status string
// Information about the attached volume of the instance.
Volume os.Volume
// IP indicates the various IP addresses which allow access.
IP []string
// Indicates whether this instance is a replica of another source instance.
ReplicaOf *Instance `mapstructure:"replica_of" json:"replica_of"`
// Indicates whether this instance is the source of other replica instances.
Replicas []Instance
}
func commonExtract(err error, body interface{}) (*Instance, error) {
if err != nil {
return nil, err
}
var response struct {
Instance Instance `mapstructure:"instance"`
}
err = mapstructure.Decode(body, &response)
val := body.(map[string]interface{})["instance"].(map[string]interface{})
if t, ok := val["created"].(string); ok && t != "" {
creationTime, err := time.Parse(time.RFC3339, t)
if err != nil {
return &response.Instance, err
}
response.Instance.Created = creationTime
}
if t, ok := val["updated"].(string); ok && t != "" {
updatedTime, err := time.Parse(time.RFC3339, t)
if err != nil {
return &response.Instance, err
}
response.Instance.Updated = updatedTime
}
return &response.Instance, err
}
// CreateResult represents the result of a Create operation.
type CreateResult struct {
os.CreateResult
}
// Extract will retrieve an instance from a create result.
func (r CreateResult) Extract() (*Instance, error) {
return commonExtract(r.Err, r.Body)
}
// GetResult represents the result of a Get operation.
type GetResult struct {
os.GetResult
}
// Extract will extract an Instance from a GetResult.
func (r GetResult) Extract() (*Instance, error) {
return commonExtract(r.Err, r.Body)
}
// ConfigResult represents the result of getting default configuration for an
// instance.
type ConfigResult struct {
gophercloud.Result
}
// DetachResult represents the result of detaching a replica from its source.
type DetachResult struct {
gophercloud.ErrResult
}
// Extract will extract the configuration information (in the form of a map)
// about a particular instance.
func (r ConfigResult) Extract() (map[string]string, error) {
if r.Err != nil {
return nil, r.Err
}
var response struct {
Instance struct {
Config map[string]string `mapstructure:"configuration"`
} `mapstructure:"instance"`
}
err := mapstructure.Decode(r.Body, &response)
return response.Instance.Config, err
}
// UpdateResult represents the result of an Update operation.
type UpdateResult struct {
gophercloud.ErrResult
}
// ExtractInstances retrieves a slice of instances from a paginated collection.
func ExtractInstances(page pagination.Page) ([]Instance, error) {
casted := page.(os.InstancePage).Body
var resp struct {
Instances []Instance `mapstructure:"instances"`
}
if err := mapstructure.Decode(casted, &resp); err != nil {
return nil, err
}
var vals []interface{}
switch casted.(type) {
case map[string]interface{}:
vals = casted.(map[string]interface{})["instances"].([]interface{})
case map[string][]interface{}:
vals = casted.(map[string][]interface{})["instances"]
default:
return resp.Instances, fmt.Errorf("Unknown type: %v", reflect.TypeOf(casted))
}
for i, v := range vals {
val := v.(map[string]interface{})
if t, ok := val["created"].(string); ok && t != "" {
creationTime, err := time.Parse(time.RFC3339, t)
if err != nil {
return resp.Instances, err
}
resp.Instances[i].Created = creationTime
}
if t, ok := val["updated"].(string); ok && t != "" {
updatedTime, err := time.Parse(time.RFC3339, t)
if err != nil {
return resp.Instances, err
}
resp.Instances[i].Updated = updatedTime
}
}
return resp.Instances, nil
}

View File

@@ -1,23 +0,0 @@
package instances
import "github.com/rackspace/gophercloud"
func baseURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("instances")
}
func createURL(c *gophercloud.ServiceClient) string {
return baseURL(c)
}
func resourceURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("instances", id)
}
func configURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("instances", id, "configuration")
}
func backupsURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("instances", id, "backups")
}

View File

@@ -1,16 +0,0 @@
package users
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/db/v1/users"
)
// Create will create a new database user for the specified database instance.
func Create(client *gophercloud.ServiceClient, instanceID string, opts os.CreateOptsBuilder) os.CreateResult {
return os.Create(client, instanceID, opts)
}
// Delete will permanently remove a user from a specified database instance.
func Delete(client *gophercloud.ServiceClient, instanceID, userName string) os.DeleteResult {
return os.Delete(client, instanceID, userName)
}

View File

@@ -1,3 +0,0 @@
// Package users provides information and interaction with the user API
// resource in the Rackspace Database service.
package users

View File

@@ -1,77 +0,0 @@
package users
const singleDB = `{"databases": [{"name": "databaseE"}]}`
var changePwdReq = `
{
"users": [
{
"name": "dbuser1",
"password": "newpassword"
},
{
"name": "dbuser2",
"password": "anotherpassword"
}
]
}
`
var updateReq = `
{
"user": {
"name": "new_username",
"password": "new_password"
}
}
`
var getResp = `
{
"user": {
"name": "exampleuser",
"host": "foo",
"databases": [
{
"name": "databaseA"
},
{
"name": "databaseB"
}
]
}
}
`
var listResp = `
{
"users": [
{
"name": "dbuser1",
"host": "localhost",
"databases": [
{
"name": "databaseA"
}
]
},
{
"name": "dbuser2",
"host": "localhost",
"databases": [
{
"name": "databaseB"
},
{
"name": "databaseC"
}
]
}
]
}
`
var (
listUserAccessResp = singleDB
grantUserAccessReq = singleDB
)

View File

@@ -1,176 +0,0 @@
package users
import (
"errors"
"github.com/rackspace/gophercloud"
db "github.com/rackspace/gophercloud/openstack/db/v1/databases"
os "github.com/rackspace/gophercloud/openstack/db/v1/users"
"github.com/rackspace/gophercloud/pagination"
)
// List will list all available users for a specified database instance.
func List(client *gophercloud.ServiceClient, instanceID string) pagination.Pager {
createPageFn := func(r pagination.PageResult) pagination.Page {
return UserPage{pagination.LinkedPageBase{PageResult: r}}
}
return pagination.NewPager(client, baseURL(client, instanceID), createPageFn)
}
/*
ChangePassword changes the password for one or more users. For example, to
change the respective passwords for two users:
opts := os.BatchCreateOpts{
os.CreateOpts{Name: "db_user_1", Password: "new_password_1"},
os.CreateOpts{Name: "db_user_2", Password: "new_password_2"},
}
ChangePassword(client, "instance_id", opts)
*/
func ChangePassword(client *gophercloud.ServiceClient, instanceID string, opts os.CreateOptsBuilder) UpdatePasswordsResult {
var res UpdatePasswordsResult
reqBody, err := opts.ToUserCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Request("PUT", baseURL(client, instanceID), gophercloud.RequestOpts{
JSONBody: &reqBody,
OkCodes: []int{202},
})
return res
}
// UpdateOpts is the struct responsible for updating an existing user.
type UpdateOpts struct {
// [OPTIONAL] Specifies a name for the user. Valid names can be composed
// of the following characters: letters (either case); numbers; these
// characters '@', '?', '#', ' ' but NEVER beginning a name string; '_' is
// permitted anywhere. Prohibited characters that are forbidden include:
// single quotes, double quotes, back quotes, semicolons, commas, backslashes,
// and forward slashes. Spaces at the front or end of a user name are also
// not permitted.
Name string
// [OPTIONAL] Specifies a password for the user.
Password string
// [OPTIONAL] Specifies the host from which a user is allowed to connect to
// the database. Possible values are a string containing an IPv4 address or
// "%" to allow connecting from any host. Optional; the default is "%".
Host string
}
// ToMap is a convenience function for creating sub-maps for individual users.
func (opts UpdateOpts) ToMap() (map[string]interface{}, error) {
if opts.Name == "root" {
return nil, errors.New("root is a reserved user name and cannot be used")
}
user := map[string]interface{}{}
if opts.Name != "" {
user["name"] = opts.Name
}
if opts.Password != "" {
user["password"] = opts.Password
}
if opts.Host != "" {
user["host"] = opts.Host
}
return user, nil
}
// Update will modify the attributes of a specified user. Attributes that can
// be updated are: user name, password, and host.
func Update(client *gophercloud.ServiceClient, instanceID, userName string, opts UpdateOpts) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToMap()
if err != nil {
res.Err = err
return res
}
reqBody = map[string]interface{}{"user": reqBody}
_, res.Err = client.Request("PUT", userURL(client, instanceID, userName), gophercloud.RequestOpts{
JSONBody: &reqBody,
OkCodes: []int{202},
})
return res
}
// Get will retrieve the details for a particular user.
func Get(client *gophercloud.ServiceClient, instanceID, userName string) GetResult {
var res GetResult
_, res.Err = client.Request("GET", userURL(client, instanceID, userName), gophercloud.RequestOpts{
JSONResponse: &res.Body,
OkCodes: []int{200},
})
return res
}
// ListAccess will list all of the databases a user has access to.
func ListAccess(client *gophercloud.ServiceClient, instanceID, userName string) pagination.Pager {
pageFn := func(r pagination.PageResult) pagination.Page {
return AccessPage{pagination.LinkedPageBase{PageResult: r}}
}
return pagination.NewPager(client, dbsURL(client, instanceID, userName), pageFn)
}
/*
GrantAccess for the specified user to one or more databases on a specified
instance. For example, to add a user to multiple databases:
opts := db.BatchCreateOpts{
db.CreateOpts{Name: "database_1"},
db.CreateOpts{Name: "database_3"},
db.CreateOpts{Name: "database_19"},
}
GrantAccess(client, "instance_id", "user_name", opts)
*/
func GrantAccess(client *gophercloud.ServiceClient, instanceID, userName string, opts db.CreateOptsBuilder) GrantAccessResult {
var res GrantAccessResult
reqBody, err := opts.ToDBCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Request("PUT", dbsURL(client, instanceID, userName), gophercloud.RequestOpts{
JSONBody: &reqBody,
OkCodes: []int{202},
})
return res
}
/*
RevokeAccess will revoke access for the specified user to one or more databases
on a specified instance. For example:
RevokeAccess(client, "instance_id", "user_name", "db_name")
*/
func RevokeAccess(client *gophercloud.ServiceClient, instanceID, userName, dbName string) RevokeAccessResult {
var res RevokeAccessResult
_, res.Err = client.Request("DELETE", dbURL(client, instanceID, userName, dbName), gophercloud.RequestOpts{
OkCodes: []int{202},
})
return res
}

View File

@@ -1,149 +0,0 @@
package users
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
db "github.com/rackspace/gophercloud/openstack/db/v1/databases"
"github.com/rackspace/gophercloud/pagination"
)
// User represents a database user
type User struct {
// The user name
Name string
// The user password
Password string
// Specifies the host from which a user is allowed to connect to the database.
// Possible values are a string containing an IPv4 address or "%" to allow
// connecting from any host.
Host string
// The databases associated with this user
Databases []db.Database
}
// UpdatePasswordsResult represents the result of changing a user password.
type UpdatePasswordsResult struct {
gophercloud.ErrResult
}
// UpdateResult represents the result of updating a user.
type UpdateResult struct {
gophercloud.ErrResult
}
// GetResult represents the result of getting a user.
type GetResult struct {
gophercloud.Result
}
// Extract will retrieve a User struct from a getresult.
func (r GetResult) Extract() (*User, error) {
if r.Err != nil {
return nil, r.Err
}
var response struct {
User User `mapstructure:"user"`
}
err := mapstructure.Decode(r.Body, &response)
return &response.User, err
}
// AccessPage represents a single page of a paginated user collection.
type AccessPage struct {
pagination.LinkedPageBase
}
// IsEmpty checks to see whether the collection is empty.
func (page AccessPage) IsEmpty() (bool, error) {
users, err := ExtractDBs(page)
if err != nil {
return true, err
}
return len(users) == 0, nil
}
// NextPageURL will retrieve the next page URL.
func (page AccessPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"databases_links"`
}
var r resp
err := mapstructure.Decode(page.Body, &r)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
}
// ExtractDBs will convert a generic pagination struct into a more
// relevant slice of DB structs.
func ExtractDBs(page pagination.Page) ([]db.Database, error) {
casted := page.(AccessPage).Body
var response struct {
DBs []db.Database `mapstructure:"databases"`
}
err := mapstructure.Decode(casted, &response)
return response.DBs, err
}
// UserPage represents a single page of a paginated user collection.
type UserPage struct {
pagination.LinkedPageBase
}
// IsEmpty checks to see whether the collection is empty.
func (page UserPage) IsEmpty() (bool, error) {
users, err := ExtractUsers(page)
if err != nil {
return true, err
}
return len(users) == 0, nil
}
// NextPageURL will retrieve the next page URL.
func (page UserPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"users_links"`
}
var r resp
err := mapstructure.Decode(page.Body, &r)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
}
// ExtractUsers will convert a generic pagination struct into a more
// relevant slice of User structs.
func ExtractUsers(page pagination.Page) ([]User, error) {
casted := page.(UserPage).Body
var response struct {
Users []User `mapstructure:"users"`
}
err := mapstructure.Decode(casted, &response)
return response.Users, err
}
// GrantAccessResult represents the result of granting access to a user.
type GrantAccessResult struct {
gophercloud.ErrResult
}
// RevokeAccessResult represents the result of revoking access to a user.
type RevokeAccessResult struct {
gophercloud.ErrResult
}

View File

@@ -1,19 +0,0 @@
package users
import "github.com/rackspace/gophercloud"
func baseURL(c *gophercloud.ServiceClient, instanceID string) string {
return c.ServiceURL("instances", instanceID, "users")
}
func userURL(c *gophercloud.ServiceClient, instanceID, userName string) string {
return c.ServiceURL("instances", instanceID, "users", userName)
}
func dbsURL(c *gophercloud.ServiceClient, instanceID, userName string) string {
return c.ServiceURL("instances", instanceID, "users", userName, "databases")
}
func dbURL(c *gophercloud.ServiceClient, instanceID, userName, dbName string) string {
return c.ServiceURL("instances", instanceID, "users", userName, "databases", dbName)
}

View File

@@ -1,24 +0,0 @@
package extensions
import (
"github.com/rackspace/gophercloud"
common "github.com/rackspace/gophercloud/openstack/common/extensions"
"github.com/rackspace/gophercloud/pagination"
)
// ExtractExtensions accepts a Page struct, specifically an ExtensionPage struct, and extracts the
// elements into a slice of os.Extension structs.
func ExtractExtensions(page pagination.Page) ([]common.Extension, error) {
return common.ExtractExtensions(page)
}
// Get retrieves information for a specific extension using its alias.
func Get(c *gophercloud.ServiceClient, alias string) common.GetResult {
return common.Get(c, alias)
}
// List returns a Pager which allows you to iterate over the full collection of extensions.
// It does not accept query parameters.
func List(c *gophercloud.ServiceClient) pagination.Pager {
return common.List(c)
}

View File

@@ -1,3 +0,0 @@
// Package extensions provides information and interaction with the all the
// extensions available for the Rackspace Identity service.
package extensions

View File

@@ -1,50 +0,0 @@
package roles
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
os "github.com/rackspace/gophercloud/openstack/identity/v2/extensions/admin/roles"
)
// List is the operation responsible for listing all available global roles
// that a user can adopt.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return os.List(client)
}
// AddUserRole is the operation responsible for assigning a particular role to
// a user. This is confined to the scope of the user's tenant - so the tenant
// ID is a required argument.
func AddUserRole(client *gophercloud.ServiceClient, userID, roleID string) UserRoleResult {
var result UserRoleResult
_, result.Err = client.Request("PUT", userRoleURL(client, userID, roleID), gophercloud.RequestOpts{
OkCodes: []int{200, 201},
})
return result
}
// DeleteUserRole is the operation responsible for deleting a particular role
// from a user. This is confined to the scope of the user's tenant - so the
// tenant ID is a required argument.
func DeleteUserRole(client *gophercloud.ServiceClient, userID, roleID string) UserRoleResult {
var result UserRoleResult
_, result.Err = client.Request("DELETE", userRoleURL(client, userID, roleID), gophercloud.RequestOpts{
OkCodes: []int{204},
})
return result
}
// UserRoleResult represents the result of either an AddUserRole or
// a DeleteUserRole operation.
type UserRoleResult struct {
gophercloud.ErrResult
}
func userRoleURL(c *gophercloud.ServiceClient, userID, roleID string) string {
return c.ServiceURL(os.UserPath, userID, os.RolePath, os.ExtPath, roleID)
}

View File

@@ -1,49 +0,0 @@
package roles
import (
"fmt"
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func MockListRoleResponse(t *testing.T) {
th.Mux.HandleFunc("/OS-KSADM/roles", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"roles": [
{
"id": "123",
"name": "compute:admin",
"description": "Nova Administrator",
"serviceId": "cke5372ebabeeabb70a0e702a4626977x4406e5"
}
]
}
`)
})
}
func MockAddUserRoleResponse(t *testing.T) {
th.Mux.HandleFunc("/users/{user_id}/roles/OS-KSADM/{role_id}", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusCreated)
})
}
func MockDeleteUserRoleResponse(t *testing.T) {
th.Mux.HandleFunc("/users/{user_id}/roles/OS-KSADM/{role_id}", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
}

View File

@@ -1,17 +0,0 @@
package tenants
import (
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
"github.com/rackspace/gophercloud/pagination"
)
// ExtractTenants interprets a page of List results as a more usable slice of Tenant structs.
func ExtractTenants(page pagination.Page) ([]os.Tenant, error) {
return os.ExtractTenants(page)
}
// List enumerates the tenants to which the current token grants access.
func List(client *gophercloud.ServiceClient, opts *os.ListOpts) pagination.Pager {
return os.List(client, opts)
}

View File

@@ -1,3 +0,0 @@
// Package tenants provides information and interaction with the tenant
// API resource for the Rackspace Identity service.
package tenants

View File

@@ -1,142 +0,0 @@
package users
import (
"errors"
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/identity/v2/users"
"github.com/rackspace/gophercloud/pagination"
)
// List returns a pager that allows traversal over a collection of users.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return os.List(client)
}
// CommonOpts are the options which are shared between CreateOpts and
// UpdateOpts
type CommonOpts struct {
// Required. The username to assign to the user. When provided, the username
// must:
// - start with an alphabetical (A-Za-z) character
// - have a minimum length of 1 character
//
// The username may contain upper and lowercase characters, as well as any of
// the following special character: . - @ _
Username string
// Required. Email address for the user account.
Email string
// Required. Indicates whether the user can authenticate after the user
// account is created. If no value is specified, the default value is true.
Enabled os.EnabledState
// Optional. The password to assign to the user. If provided, the password
// must:
// - start with an alphabetical (A-Za-z) character
// - have a minimum length of 8 characters
// - contain at least one uppercase character, one lowercase character, and
// one numeric character.
//
// The password may contain any of the following special characters: . - @ _
Password string
}
// CreateOpts represents the options needed when creating new users.
type CreateOpts CommonOpts
// ToUserCreateMap assembles a request body based on the contents of a CreateOpts.
func (opts CreateOpts) ToUserCreateMap() (map[string]interface{}, error) {
m := make(map[string]interface{})
if opts.Username == "" {
return m, errors.New("Username is a required field")
}
if opts.Enabled == nil {
return m, errors.New("Enabled is a required field")
}
if opts.Email == "" {
return m, errors.New("Email is a required field")
}
if opts.Username != "" {
m["username"] = opts.Username
}
if opts.Email != "" {
m["email"] = opts.Email
}
if opts.Enabled != nil {
m["enabled"] = opts.Enabled
}
if opts.Password != "" {
m["OS-KSADM:password"] = opts.Password
}
return map[string]interface{}{"user": m}, nil
}
// Create is the operation responsible for creating new users.
func Create(client *gophercloud.ServiceClient, opts os.CreateOptsBuilder) CreateResult {
return CreateResult{os.Create(client, opts)}
}
// Get requests details on a single user, either by ID.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
return GetResult{os.Get(client, id)}
}
// UpdateOptsBuilder allows extensions to add additional attributes to the Update request.
type UpdateOptsBuilder interface {
ToUserUpdateMap() map[string]interface{}
}
// UpdateOpts specifies the base attributes that may be updated on an existing server.
type UpdateOpts CommonOpts
// ToUserUpdateMap formats an UpdateOpts structure into a request body.
func (opts UpdateOpts) ToUserUpdateMap() map[string]interface{} {
m := make(map[string]interface{})
if opts.Username != "" {
m["username"] = opts.Username
}
if opts.Enabled != nil {
m["enabled"] = &opts.Enabled
}
if opts.Email != "" {
m["email"] = opts.Email
}
return map[string]interface{}{"user": m}
}
// Update is the operation responsible for updating exist users by their UUID.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
var result UpdateResult
_, result.Err = client.Request("POST", os.ResourceURL(client, id), gophercloud.RequestOpts{
JSONResponse: &result.Body,
JSONBody: opts.ToUserUpdateMap(),
OkCodes: []int{200},
})
return result
}
// Delete is the operation responsible for permanently deleting an API user.
func Delete(client *gophercloud.ServiceClient, id string) os.DeleteResult {
return os.Delete(client, id)
}
// ResetAPIKey resets the User's API key.
func ResetAPIKey(client *gophercloud.ServiceClient, id string) ResetAPIKeyResult {
var result ResetAPIKeyResult
_, result.Err = client.Request("POST", resetAPIKeyURL(client, id), gophercloud.RequestOpts{
JSONResponse: &result.Body,
OkCodes: []int{200},
})
return result
}

View File

@@ -1,154 +0,0 @@
package users
import (
"fmt"
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func mockListResponse(t *testing.T) {
th.Mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"users":[
{
"id": "u1000",
"username": "jqsmith",
"email": "john.smith@example.org",
"enabled": true
},
{
"id": "u1001",
"username": "jqsmith",
"email": "jane.smith@example.org",
"enabled": true
}
]
}
`)
})
}
func mockCreateUser(t *testing.T) {
th.Mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"user": {
"username": "new_user",
"enabled": false,
"email": "new_user@foo.com",
"OS-KSADM:password": "foo"
}
}
`)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"user": {
"RAX-AUTH:defaultRegion": "DFW",
"RAX-AUTH:domainId": "5830280",
"id": "123456",
"username": "new_user",
"email": "new_user@foo.com",
"enabled": false
}
}
`)
})
}
func mockGetUser(t *testing.T) {
th.Mux.HandleFunc("/users/new_user", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"user": {
"RAX-AUTH:defaultRegion": "DFW",
"RAX-AUTH:domainId": "5830280",
"RAX-AUTH:multiFactorEnabled": "true",
"id": "c39e3de9be2d4c779f1dfd6abacc176d",
"username": "jqsmith",
"email": "john.smith@example.org",
"enabled": true
}
}
`)
})
}
func mockUpdateUser(t *testing.T) {
th.Mux.HandleFunc("/users/c39e3de9be2d4c779f1dfd6abacc176d", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"user": {
"email": "new_email@foo.com",
"enabled": true
}
}
`)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"user": {
"RAX-AUTH:defaultRegion": "DFW",
"RAX-AUTH:domainId": "5830280",
"RAX-AUTH:multiFactorEnabled": "true",
"id": "123456",
"username": "jqsmith",
"email": "new_email@foo.com",
"enabled": true
}
}
`)
})
}
func mockDeleteUser(t *testing.T) {
th.Mux.HandleFunc("/users/c39e3de9be2d4c779f1dfd6abacc176d", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
}
func mockResetAPIKey(t *testing.T) {
th.Mux.HandleFunc("/users/99/OS-KSADM/credentials/RAX-KSKEY:apiKeyCredentials/RAX-AUTH/reset", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"RAX-KSKEY:apiKeyCredentials": {
"username": "joesmith",
"apiKey": "mooH1eiLahd5ahYood7r"
}
}`)
})
}

View File

@@ -1,129 +0,0 @@
package users
import (
"strconv"
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/identity/v2/users"
"github.com/mitchellh/mapstructure"
)
// User represents a user resource that exists on the API.
type User struct {
// The UUID for this user.
ID string
// The human name for this user.
Name string
// The username for this user.
Username string
// Indicates whether the user is enabled (true) or disabled (false).
Enabled bool
// The email address for this user.
Email string
// The ID of the tenant to which this user belongs.
TenantID string `mapstructure:"tenant_id"`
// Specifies the default region for the user account. This value is inherited
// from the user administrator when the account is created.
DefaultRegion string `mapstructure:"RAX-AUTH:defaultRegion"`
// Identifies the domain that contains the user account. This value is
// inherited from the user administrator when the account is created.
DomainID string `mapstructure:"RAX-AUTH:domainId"`
// The password value that the user needs for authentication. If the Add user
// request included a password value, this attribute is not included in the
// response.
Password string `mapstructure:"OS-KSADM:password"`
// Indicates whether the user has enabled multi-factor authentication.
MultiFactorEnabled bool `mapstructure:"RAX-AUTH:multiFactorEnabled"`
}
// CreateResult represents the result of a Create operation
type CreateResult struct {
os.CreateResult
}
// GetResult represents the result of a Get operation
type GetResult struct {
os.GetResult
}
// UpdateResult represents the result of an Update operation
type UpdateResult struct {
os.UpdateResult
}
func commonExtract(resp interface{}, err error) (*User, error) {
if err != nil {
return nil, err
}
var respStruct struct {
User *User `json:"user"`
}
// Since the API returns a string instead of a bool, we need to hack the JSON
json := resp.(map[string]interface{})
user := json["user"].(map[string]interface{})
if s, ok := user["RAX-AUTH:multiFactorEnabled"].(string); ok && s != "" {
if b, err := strconv.ParseBool(s); err == nil {
user["RAX-AUTH:multiFactorEnabled"] = b
}
}
err = mapstructure.Decode(json, &respStruct)
return respStruct.User, err
}
// Extract will get the Snapshot object out of the GetResult object.
func (r GetResult) Extract() (*User, error) {
return commonExtract(r.Body, r.Err)
}
// Extract will get the Snapshot object out of the CreateResult object.
func (r CreateResult) Extract() (*User, error) {
return commonExtract(r.Body, r.Err)
}
// Extract will get the Snapshot object out of the UpdateResult object.
func (r UpdateResult) Extract() (*User, error) {
return commonExtract(r.Body, r.Err)
}
// ResetAPIKeyResult represents the server response to the ResetAPIKey method.
type ResetAPIKeyResult struct {
gophercloud.Result
}
// ResetAPIKeyValue represents an API Key that has been reset.
type ResetAPIKeyValue struct {
// The Username for this API Key reset.
Username string `mapstructure:"username"`
// The new API Key for this user.
APIKey string `mapstructure:"apiKey"`
}
// Extract will get the Error or ResetAPIKeyValue object out of the ResetAPIKeyResult object.
func (r ResetAPIKeyResult) Extract() (*ResetAPIKeyValue, error) {
if r.Err != nil {
return nil, r.Err
}
var response struct {
ResetAPIKeyValue ResetAPIKeyValue `mapstructure:"RAX-KSKEY:apiKeyCredentials"`
}
err := mapstructure.Decode(r.Body, &response)
return &response.ResetAPIKeyValue, err
}

View File

@@ -1,7 +0,0 @@
package users
import "github.com/rackspace/gophercloud"
func resetAPIKeyURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("users", id, "OS-KSADM", "credentials", "RAX-KSKEY:apiKeyCredentials", "RAX-AUTH", "reset")
}

View File

@@ -1,12 +0,0 @@
/*
Package acl provides information and interaction with the access lists feature
of the Rackspace Cloud Load Balancer service.
The access list management feature allows fine-grained network access controls
to be applied to the load balancer's virtual IP address. A single IP address,
multiple IP addresses, or entire network subnets can be added. Items that are
configured with the ALLOW type always takes precedence over items with the DENY
type. To reject traffic from all items except for those with the ALLOW type,
add a networkItem with an address of "0.0.0.0/0" and a DENY type.
*/
package acl

View File

@@ -1,109 +0,0 @@
package acl
import (
"fmt"
"net/http"
"strconv"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func _rootURL(lbID int) string {
return "/loadbalancers/" + strconv.Itoa(lbID) + "/accesslist"
}
func mockListResponse(t *testing.T, id int) {
th.Mux.HandleFunc(_rootURL(id), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"accessList": [
{
"address": "206.160.163.21",
"id": 21,
"type": "DENY"
},
{
"address": "206.160.163.22",
"id": 22,
"type": "DENY"
},
{
"address": "206.160.163.23",
"id": 23,
"type": "DENY"
},
{
"address": "206.160.163.24",
"id": 24,
"type": "DENY"
}
]
}
`)
})
}
func mockCreateResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"accessList": [
{
"address": "206.160.163.21",
"type": "DENY"
},
{
"address": "206.160.165.11",
"type": "DENY"
}
]
}
`)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted)
})
}
func mockDeleteAllResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusAccepted)
})
}
func mockBatchDeleteResponse(t *testing.T, lbID int, ids []int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
r.ParseForm()
for k, v := range ids {
fids := r.Form["id"]
th.AssertEquals(t, strconv.Itoa(v), fids[k])
}
w.WriteHeader(http.StatusAccepted)
})
}
func mockDeleteResponse(t *testing.T, lbID, networkID int) {
th.Mux.HandleFunc(_rootURL(lbID)+"/"+strconv.Itoa(networkID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusAccepted)
})
}

View File

@@ -1,111 +0,0 @@
package acl
import (
"errors"
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// List is the operation responsible for returning a paginated collection of
// network items that define a load balancer's access list.
func List(client *gophercloud.ServiceClient, lbID int) pagination.Pager {
url := rootURL(client, lbID)
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return AccessListPage{pagination.SinglePageBase(r)}
})
}
// CreateOptsBuilder is the interface responsible for generating the JSON
// for a Create operation.
type CreateOptsBuilder interface {
ToAccessListCreateMap() (map[string]interface{}, error)
}
// CreateOpts is a slice of CreateOpt structs, that allow the user to create
// multiple nodes in a single operation (one node per CreateOpt).
type CreateOpts []CreateOpt
// CreateOpt represents the options to create a single node.
type CreateOpt struct {
// Required - the IP address or CIDR for item to add to access list.
Address string
// Required - the type of the node. Either ALLOW or DENY.
Type Type
}
// ToAccessListCreateMap converts a slice of options into a map that can be
// used for the JSON.
func (opts CreateOpts) ToAccessListCreateMap() (map[string]interface{}, error) {
type itemMap map[string]interface{}
items := []itemMap{}
for k, v := range opts {
if v.Address == "" {
return itemMap{}, fmt.Errorf("Address is a required attribute, none provided for %d CreateOpt element", k)
}
if v.Type != ALLOW && v.Type != DENY {
return itemMap{}, fmt.Errorf("Type must be ALLOW or DENY")
}
item := make(itemMap)
item["address"] = v.Address
item["type"] = v.Type
items = append(items, item)
}
return itemMap{"accessList": items}, nil
}
// Create is the operation responsible for adding network items to the access
// rules for a particular load balancer. If network items already exist, the
// new item will be appended. A single IP address or subnet range is considered
// unique and cannot be duplicated.
func Create(client *gophercloud.ServiceClient, loadBalancerID int, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToAccessListCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Post(rootURL(client, loadBalancerID), reqBody, nil, nil)
return res
}
// BulkDelete will delete multiple network items from a load balancer's access
// list in a single operation.
func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, itemIDs []int) DeleteResult {
var res DeleteResult
if len(itemIDs) > 10 || len(itemIDs) == 0 {
res.Err = errors.New("You must provide a minimum of 1 and a maximum of 10 item IDs")
return res
}
url := rootURL(c, loadBalancerID)
url += gophercloud.IDSliceToQueryString("id", itemIDs)
_, res.Err = c.Delete(url, nil)
return res
}
// Delete will remove a single network item from a load balancer's access list.
func Delete(c *gophercloud.ServiceClient, lbID, itemID int) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(resourceURL(c, lbID, itemID), nil)
return res
}
// DeleteAll will delete the entire contents of a load balancer's access list,
// effectively resetting it and allowing all traffic.
func DeleteAll(c *gophercloud.ServiceClient, lbID int) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(rootURL(c, lbID), nil)
return res
}

View File

@@ -1,72 +0,0 @@
package acl
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// AccessList represents the rules of network access to a particular load
// balancer.
type AccessList []NetworkItem
// NetworkItem describes how an IP address or entire subnet may interact with a
// load balancer.
type NetworkItem struct {
// The IP address or subnet (CIDR) that defines the network item.
Address string
// The numeric unique ID for this item.
ID int
// Either ALLOW or DENY.
Type Type
}
// Type defines how an item may connect to the load balancer.
type Type string
// Convenience consts.
const (
ALLOW Type = "ALLOW"
DENY Type = "DENY"
)
// AccessListPage is the page returned by a pager for traversing over a
// collection of network items in an access list.
type AccessListPage struct {
pagination.SinglePageBase
}
// IsEmpty checks whether an AccessListPage struct is empty.
func (p AccessListPage) IsEmpty() (bool, error) {
is, err := ExtractAccessList(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
}
// ExtractAccessList accepts a Page struct, specifically an AccessListPage
// struct, and extracts the elements into a slice of NetworkItem structs. In
// other words, a generic collection is mapped into a relevant slice.
func ExtractAccessList(page pagination.Page) (AccessList, error) {
var resp struct {
List AccessList `mapstructure:"accessList" json:"accessList"`
}
err := mapstructure.Decode(page.(AccessListPage).Body, &resp)
return resp.List, err
}
// CreateResult represents the result of a create operation.
type CreateResult struct {
gophercloud.ErrResult
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@@ -1,20 +0,0 @@
package acl
import (
"strconv"
"github.com/rackspace/gophercloud"
)
const (
path = "loadbalancers"
aclPath = "accesslist"
)
func resourceURL(c *gophercloud.ServiceClient, lbID, networkID int) string {
return c.ServiceURL(path, strconv.Itoa(lbID), aclPath, strconv.Itoa(networkID))
}
func rootURL(c *gophercloud.ServiceClient, lbID int) string {
return c.ServiceURL(path, strconv.Itoa(lbID), aclPath)
}

View File

@@ -1,44 +0,0 @@
/*
Package lbs provides information and interaction with the Load Balancer API
resource for the Rackspace Cloud Load Balancer service.
A load balancer is a logical device which belongs to a cloud account. It is
used to distribute workloads between multiple back-end systems or services,
based on the criteria defined as part of its configuration. This configuration
is defined using the Create operation, and can be updated with Update.
To conserve IPv4 address space, it is highly recommended that you share Virtual
IPs between load balancers. If you have at least one load balancer, you may
create subsequent ones that share a single virtual IPv4 and/or a single IPv6 by
passing in a virtual IP ID to the Update operation (instead of a type). This
feature is also highly desirable if you wish to load balance both an insecure
and secure protocol using one IP or DNS name. In order to share a virtual IP,
each Load Balancer must utilize a unique port.
All load balancers have a Status attribute that shows the current configuration
status of the device. This status is immutable by the caller and is updated
automatically based on state changes within the service. When a load balancer
is first created, it is placed into a BUILD state while the configuration is
being generated and applied based on the request. Once the configuration is
applied and finalized, it is in an ACTIVE status. In the event of a
configuration change or update, the status of the load balancer changes to
PENDING_UPDATE to signify configuration changes are in progress but have not yet
been finalized. Load balancers in a SUSPENDED status are configured to reject
traffic and do not forward requests to back-end nodes.
An HTTP load balancer has the X-Forwarded-For (XFF) HTTP header set by default.
This header contains the originating IP address of a client connecting to a web
server through an HTTP proxy or load balancer, which many web applications are
already designed to use when determining the source address for a request.
It also includes the X-Forwarded-Proto (XFP) HTTP header, which has been added
for identifying the originating protocol of an HTTP request as "http" or
"https" depending on which protocol the client requested. This is useful when
using SSL termination.
Finally, it also includes the X-Forwarded-Port HTTP header, which has been
added for being able to generate secure URLs containing the specified port.
This header, along with the X-Forwarded-For header, provides the needed
information to the underlying application servers.
*/
package lbs

View File

@@ -1,584 +0,0 @@
package lbs
import (
"fmt"
"net/http"
"strconv"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func mockListLBResponse(t *testing.T) {
th.Mux.HandleFunc("/loadbalancers", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"loadBalancers":[
{
"name":"lb-site1",
"id":71,
"protocol":"HTTP",
"port":80,
"algorithm":"RANDOM",
"status":"ACTIVE",
"nodeCount":3,
"virtualIps":[
{
"id":403,
"address":"206.55.130.1",
"type":"PUBLIC",
"ipVersion":"IPV4"
}
],
"created":{
"time":"2010-11-30T03:23:42Z"
},
"updated":{
"time":"2010-11-30T03:23:44Z"
}
},
{
"name":"lb-site2",
"id":72,
"created":{
"time":"2011-11-30T03:23:42Z"
},
"updated":{
"time":"2011-11-30T03:23:44Z"
}
},
{
"name":"lb-site3",
"id":73,
"created":{
"time":"2012-11-30T03:23:42Z"
},
"updated":{
"time":"2012-11-30T03:23:44Z"
}
}
]
}
`)
})
}
func mockCreateLBResponse(t *testing.T) {
th.Mux.HandleFunc("/loadbalancers", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"loadBalancer": {
"name": "a-new-loadbalancer",
"port": 80,
"protocol": "HTTP",
"virtualIps": [
{
"id": 2341
},
{
"id": 900001
}
],
"nodes": [
{
"address": "10.1.1.1",
"port": 80,
"condition": "ENABLED"
}
]
}
}
`)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted)
fmt.Fprintf(w, `
{
"loadBalancer": {
"name": "a-new-loadbalancer",
"id": 144,
"protocol": "HTTP",
"halfClosed": false,
"port": 83,
"algorithm": "RANDOM",
"status": "BUILD",
"timeout": 30,
"cluster": {
"name": "ztm-n01.staging1.lbaas.rackspace.net"
},
"nodes": [
{
"address": "10.1.1.1",
"id": 653,
"port": 80,
"status": "ONLINE",
"condition": "ENABLED",
"weight": 1
}
],
"virtualIps": [
{
"address": "206.10.10.210",
"id": 39,
"type": "PUBLIC",
"ipVersion": "IPV4"
},
{
"address": "2001:4801:79f1:0002:711b:be4c:0000:0021",
"id": 900001,
"type": "PUBLIC",
"ipVersion": "IPV6"
}
],
"created": {
"time": "2010-11-30T03:23:42Z"
},
"updated": {
"time": "2010-11-30T03:23:44Z"
},
"connectionLogging": {
"enabled": false
}
}
}
`)
})
}
func mockBatchDeleteLBResponse(t *testing.T, ids []int) {
th.Mux.HandleFunc("/loadbalancers", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
r.ParseForm()
for k, v := range ids {
fids := r.Form["id"]
th.AssertEquals(t, strconv.Itoa(v), fids[k])
}
w.WriteHeader(http.StatusAccepted)
})
}
func mockDeleteLBResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusAccepted)
})
}
func mockGetLBResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"loadBalancer": {
"id": 2000,
"name": "sample-loadbalancer",
"protocol": "HTTP",
"port": 80,
"algorithm": "RANDOM",
"status": "ACTIVE",
"timeout": 30,
"connectionLogging": {
"enabled": true
},
"virtualIps": [
{
"id": 1000,
"address": "206.10.10.210",
"type": "PUBLIC",
"ipVersion": "IPV4"
}
],
"nodes": [
{
"id": 1041,
"address": "10.1.1.1",
"port": 80,
"condition": "ENABLED",
"status": "ONLINE"
},
{
"id": 1411,
"address": "10.1.1.2",
"port": 80,
"condition": "ENABLED",
"status": "ONLINE"
}
],
"sessionPersistence": {
"persistenceType": "HTTP_COOKIE"
},
"connectionThrottle": {
"maxConnections": 100
},
"cluster": {
"name": "c1.dfw1"
},
"created": {
"time": "2010-11-30T03:23:42Z"
},
"updated": {
"time": "2010-11-30T03:23:44Z"
},
"sourceAddresses": {
"ipv6Public": "2001:4801:79f1:1::1/64",
"ipv4Servicenet": "10.0.0.0",
"ipv4Public": "10.12.99.28"
}
}
}
`)
})
}
func mockUpdateLBResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"loadBalancer": {
"name": "a-new-loadbalancer",
"protocol": "TCP",
"halfClosed": true,
"algorithm": "RANDOM",
"port": 8080,
"timeout": 100,
"httpsRedirect": false
}
}
`)
w.WriteHeader(http.StatusAccepted)
})
}
func mockListProtocolsResponse(t *testing.T) {
th.Mux.HandleFunc("/loadbalancers/protocols", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"protocols": [
{
"name": "DNS_TCP",
"port": 53
},
{
"name": "DNS_UDP",
"port": 53
},
{
"name": "FTP",
"port": 21
},
{
"name": "HTTP",
"port": 80
},
{
"name": "HTTPS",
"port": 443
},
{
"name": "IMAPS",
"port": 993
},
{
"name": "IMAPv4",
"port": 143
},
{
"name": "LDAP",
"port": 389
},
{
"name": "LDAPS",
"port": 636
},
{
"name": "MYSQL",
"port": 3306
},
{
"name": "POP3",
"port": 110
},
{
"name": "POP3S",
"port": 995
},
{
"name": "SMTP",
"port": 25
},
{
"name": "TCP",
"port": 0
},
{
"name": "TCP_CLIENT_FIRST",
"port": 0
},
{
"name": "UDP",
"port": 0
},
{
"name": "UDP_STREAM",
"port": 0
},
{
"name": "SFTP",
"port": 22
},
{
"name": "TCP_STREAM",
"port": 0
}
]
}
`)
})
}
func mockListAlgorithmsResponse(t *testing.T) {
th.Mux.HandleFunc("/loadbalancers/algorithms", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"algorithms": [
{
"name": "LEAST_CONNECTIONS"
},
{
"name": "RANDOM"
},
{
"name": "ROUND_ROBIN"
},
{
"name": "WEIGHTED_LEAST_CONNECTIONS"
},
{
"name": "WEIGHTED_ROUND_ROBIN"
}
]
}
`)
})
}
func mockGetLoggingResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id)+"/connectionlogging", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"connectionLogging": {
"enabled": true
}
}
`)
})
}
func mockEnableLoggingResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id)+"/connectionlogging", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"connectionLogging":{
"enabled":true
}
}
`)
w.WriteHeader(http.StatusAccepted)
})
}
func mockDisableLoggingResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id)+"/connectionlogging", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"connectionLogging":{
"enabled":false
}
}
`)
w.WriteHeader(http.StatusAccepted)
})
}
func mockGetErrorPageResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id)+"/errorpage", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"errorpage": {
"content": "<html>DEFAULT ERROR PAGE</html>"
}
}
`)
})
}
func mockSetErrorPageResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id)+"/errorpage", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"errorpage": {
"content": "<html>New error page</html>"
}
}
`)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"errorpage": {
"content": "<html>New error page</html>"
}
}
`)
})
}
func mockDeleteErrorPageResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id)+"/errorpage", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusOK)
})
}
func mockGetStatsResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id)+"/stats", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"connectTimeOut": 10,
"connectError": 20,
"connectFailure": 30,
"dataTimedOut": 40,
"keepAliveTimedOut": 50,
"maxConn": 60,
"currentConn": 40,
"connectTimeOutSsl": 10,
"connectErrorSsl": 20,
"connectFailureSsl": 30,
"dataTimedOutSsl": 40,
"keepAliveTimedOutSsl": 50,
"maxConnSsl": 60,
"currentConnSsl": 40
}
`)
})
}
func mockGetCachingResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id)+"/contentcaching", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"contentCaching": {
"enabled": true
}
}
`)
})
}
func mockEnableCachingResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id)+"/contentcaching", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"contentCaching":{
"enabled":true
}
}
`)
w.WriteHeader(http.StatusAccepted)
})
}
func mockDisableCachingResponse(t *testing.T, id int) {
th.Mux.HandleFunc("/loadbalancers/"+strconv.Itoa(id)+"/contentcaching", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"contentCaching":{
"enabled":false
}
}
`)
w.WriteHeader(http.StatusAccepted)
})
}

View File

@@ -1,497 +0,0 @@
package lbs
import (
"errors"
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/lb/v1/acl"
"github.com/rackspace/gophercloud/rackspace/lb/v1/monitors"
"github.com/rackspace/gophercloud/rackspace/lb/v1/nodes"
"github.com/rackspace/gophercloud/rackspace/lb/v1/sessions"
"github.com/rackspace/gophercloud/rackspace/lb/v1/throttle"
"github.com/rackspace/gophercloud/rackspace/lb/v1/vips"
)
var (
errNameRequired = errors.New("Name is a required attribute")
errTimeoutExceeded = errors.New("Timeout must be less than 120")
)
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToLBListQuery() (string, error)
}
// ListOpts allows the filtering and sorting of paginated collections through
// the API.
type ListOpts struct {
ChangesSince string `q:"changes-since"`
Status Status `q:"status"`
NodeAddr string `q:"nodeaddress"`
Marker string `q:"marker"`
Limit int `q:"limit"`
}
// ToLBListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToLBListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// List is the operation responsible for returning a paginated collection of
// load balancers. You may pass in a ListOpts struct to filter results.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := rootURL(client)
if opts != nil {
query, err := opts.ToLBListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return LBPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// 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 {
ToLBCreateMap() (map[string]interface{}, error)
}
// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts struct {
// Required - name of the load balancer to create. The name must be 128
// characters or fewer in length, and all UTF-8 characters are valid.
Name string
// Optional - nodes to be added.
Nodes []nodes.Node
// Required - protocol of the service that is being load balanced.
// See http://docs.rackspace.com/loadbalancers/api/v1.0/clb-devguide/content/protocols.html
// for a full list of supported protocols.
Protocol string
// Optional - enables or disables Half-Closed support for the load balancer.
// Half-Closed support provides the ability for one end of the connection to
// terminate its output, while still receiving data from the other end. Only
// available for TCP/TCP_CLIENT_FIRST protocols.
HalfClosed gophercloud.EnabledState
// Optional - the type of virtual IPs you want associated with the load
// balancer.
VIPs []vips.VIP
// Optional - the access list management feature allows fine-grained network
// access controls to be applied to the load balancer virtual IP address.
AccessList *acl.AccessList
// Optional - algorithm that defines how traffic should be directed between
// back-end nodes.
Algorithm string
// Optional - current connection logging configuration.
ConnectionLogging *ConnectionLogging
// Optional - specifies a limit on the number of connections per IP address
// to help mitigate malicious or abusive traffic to your applications.
ConnThrottle *throttle.ConnectionThrottle
// Optional - the type of health monitor check to perform to ensure that the
// service is performing properly.
HealthMonitor *monitors.Monitor
// Optional - arbitrary information that can be associated with each LB.
Metadata map[string]interface{}
// Optional - port number for the service you are load balancing.
Port int
// Optional - the timeout value for the load balancer and communications with
// its nodes. Defaults to 30 seconds with a maximum of 120 seconds.
Timeout int
// Optional - specifies whether multiple requests from clients are directed
// to the same node.
SessionPersistence *sessions.SessionPersistence
// Optional - enables or disables HTTP to HTTPS redirection for the load
// balancer. When enabled, any HTTP request returns status code 301 (Moved
// Permanently), and the requester is redirected to the requested URL via the
// HTTPS protocol on port 443. For example, http://example.com/page.html
// would be redirected to https://example.com/page.html. Only available for
// HTTPS protocol (port=443), or HTTP protocol with a properly configured SSL
// termination (secureTrafficOnly=true, securePort=443).
HTTPSRedirect gophercloud.EnabledState
}
// ToLBCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToLBCreateMap() (map[string]interface{}, error) {
lb := make(map[string]interface{})
if opts.Name == "" {
return lb, errNameRequired
}
if opts.Timeout > 120 {
return lb, errTimeoutExceeded
}
lb["name"] = opts.Name
if len(opts.Nodes) > 0 {
nodes := []map[string]interface{}{}
for _, n := range opts.Nodes {
nodes = append(nodes, map[string]interface{}{
"address": n.Address,
"port": n.Port,
"condition": n.Condition,
})
}
lb["nodes"] = nodes
}
if opts.Protocol != "" {
lb["protocol"] = opts.Protocol
}
if opts.HalfClosed != nil {
lb["halfClosed"] = opts.HalfClosed
}
if len(opts.VIPs) > 0 {
lb["virtualIps"] = opts.VIPs
}
if opts.AccessList != nil {
lb["accessList"] = &opts.AccessList
}
if opts.Algorithm != "" {
lb["algorithm"] = opts.Algorithm
}
if opts.ConnectionLogging != nil {
lb["connectionLogging"] = &opts.ConnectionLogging
}
if opts.ConnThrottle != nil {
lb["connectionThrottle"] = &opts.ConnThrottle
}
if opts.HealthMonitor != nil {
lb["healthMonitor"] = &opts.HealthMonitor
}
if len(opts.Metadata) != 0 {
lb["metadata"] = opts.Metadata
}
if opts.Port > 0 {
lb["port"] = opts.Port
}
if opts.Timeout > 0 {
lb["timeout"] = opts.Timeout
}
if opts.SessionPersistence != nil {
lb["sessionPersistence"] = &opts.SessionPersistence
}
if opts.HTTPSRedirect != nil {
lb["httpsRedirect"] = &opts.HTTPSRedirect
}
return map[string]interface{}{"loadBalancer": lb}, nil
}
// Create is the operation responsible for asynchronously provisioning a new
// load balancer based on the configuration defined in CreateOpts. Once the
// request is validated and progress has started on the provisioning process, a
// response struct is returned. When extracted (with Extract()), you have
// to the load balancer's unique ID and status.
//
// Once an ID is attained, you can check on the progress of the operation by
// calling Get and passing in the ID. If the corresponding request cannot be
// fulfilled due to insufficient or invalid data, an HTTP 400 (Bad Request)
// error response is returned with information regarding the nature of the
// failure in the body of the response. Failures in the validation process are
// non-recoverable and require the caller to correct the cause of the failure.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToLBCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
return res
}
// Get is the operation responsible for providing detailed information
// regarding a specific load balancer which is configured and associated with
// your account. This operation is not capable of returning details for a load
// balancer which has been deleted.
func Get(c *gophercloud.ServiceClient, id int) GetResult {
var res GetResult
_, res.Err = c.Get(resourceURL(c, id), &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}
// BulkDelete removes all the load balancers referenced in the slice of IDs.
// Any and all configuration data associated with these load balancers is
// immediately purged and is not recoverable.
//
// If one of the items in the list cannot be removed due to its current status,
// a 400 Bad Request error is returned along with the IDs of the ones the
// system identified as potential failures for this request.
func BulkDelete(c *gophercloud.ServiceClient, ids []int) DeleteResult {
var res DeleteResult
if len(ids) > 10 || len(ids) == 0 {
res.Err = errors.New("You must provide a minimum of 1 and a maximum of 10 LB IDs")
return res
}
url := rootURL(c)
url += gophercloud.IDSliceToQueryString("id", ids)
_, res.Err = c.Delete(url, nil)
return res
}
// Delete removes a single load balancer.
func Delete(c *gophercloud.ServiceClient, id int) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(resourceURL(c, id), nil)
return res
}
// UpdateOptsBuilder represents a type that can be converted into a JSON-like
// map structure.
type UpdateOptsBuilder interface {
ToLBUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts represents the options for updating an existing load balancer.
type UpdateOpts struct {
// Optional - new name of the load balancer.
Name string
// Optional - the new protocol you want your load balancer to have.
// See http://docs.rackspace.com/loadbalancers/api/v1.0/clb-devguide/content/protocols.html
// for a full list of supported protocols.
Protocol string
// Optional - see the HalfClosed field in CreateOpts for more information.
HalfClosed gophercloud.EnabledState
// Optional - see the Algorithm field in CreateOpts for more information.
Algorithm string
// Optional - see the Port field in CreateOpts for more information.
Port int
// Optional - see the Timeout field in CreateOpts for more information.
Timeout int
// Optional - see the HTTPSRedirect field in CreateOpts for more information.
HTTPSRedirect gophercloud.EnabledState
}
// ToLBUpdateMap casts an UpdateOpts struct to a map.
func (opts UpdateOpts) ToLBUpdateMap() (map[string]interface{}, error) {
lb := make(map[string]interface{})
if opts.Name != "" {
lb["name"] = opts.Name
}
if opts.Protocol != "" {
lb["protocol"] = opts.Protocol
}
if opts.HalfClosed != nil {
lb["halfClosed"] = opts.HalfClosed
}
if opts.Algorithm != "" {
lb["algorithm"] = opts.Algorithm
}
if opts.Port > 0 {
lb["port"] = opts.Port
}
if opts.Timeout > 0 {
lb["timeout"] = opts.Timeout
}
if opts.HTTPSRedirect != nil {
lb["httpsRedirect"] = &opts.HTTPSRedirect
}
return map[string]interface{}{"loadBalancer": lb}, nil
}
// Update is the operation responsible for asynchronously updating the
// attributes of a specific load balancer. Upon successful validation of the
// request, the service returns a 202 Accepted response, and the load balancer
// enters a PENDING_UPDATE state. A user can poll the load balancer with Get to
// wait for the changes to be applied. When this happens, the load balancer will
// return to an ACTIVE state.
func Update(c *gophercloud.ServiceClient, id int, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToLBUpdateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = c.Put(resourceURL(c, id), reqBody, nil, nil)
return res
}
// ListProtocols is the operation responsible for returning a paginated
// collection of load balancer protocols.
func ListProtocols(client *gophercloud.ServiceClient) pagination.Pager {
url := protocolsURL(client)
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return ProtocolPage{pagination.SinglePageBase(r)}
})
}
// ListAlgorithms is the operation responsible for returning a paginated
// collection of load balancer algorithms.
func ListAlgorithms(client *gophercloud.ServiceClient) pagination.Pager {
url := algorithmsURL(client)
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return AlgorithmPage{pagination.SinglePageBase(r)}
})
}
// IsLoggingEnabled returns true if the load balancer has connection logging
// enabled and false if not.
func IsLoggingEnabled(client *gophercloud.ServiceClient, id int) (bool, error) {
var body interface{}
_, err := client.Get(loggingURL(client, id), &body, nil)
if err != nil {
return false, err
}
var resp struct {
CL struct {
Enabled bool `mapstructure:"enabled"`
} `mapstructure:"connectionLogging"`
}
err = mapstructure.Decode(body, &resp)
return resp.CL.Enabled, err
}
func toConnLoggingMap(state bool) map[string]map[string]bool {
return map[string]map[string]bool{
"connectionLogging": map[string]bool{"enabled": state},
}
}
// EnableLogging will enable connection logging for a specified load balancer.
func EnableLogging(client *gophercloud.ServiceClient, id int) gophercloud.ErrResult {
var res gophercloud.ErrResult
_, res.Err = client.Put(loggingURL(client, id), toConnLoggingMap(true), nil, nil)
return res
}
// DisableLogging will disable connection logging for a specified load balancer.
func DisableLogging(client *gophercloud.ServiceClient, id int) gophercloud.ErrResult {
var res gophercloud.ErrResult
_, res.Err = client.Put(loggingURL(client, id), toConnLoggingMap(false), nil, nil)
return res
}
// GetErrorPage will retrieve the current error page for the load balancer.
func GetErrorPage(client *gophercloud.ServiceClient, id int) ErrorPageResult {
var res ErrorPageResult
_, res.Err = client.Get(errorPageURL(client, id), &res.Body, nil)
return res
}
// SetErrorPage will set the HTML of the load balancer's error page to a
// specific value.
func SetErrorPage(client *gophercloud.ServiceClient, id int, html string) ErrorPageResult {
var res ErrorPageResult
type stringMap map[string]string
reqBody := map[string]stringMap{"errorpage": stringMap{"content": html}}
_, res.Err = client.Put(errorPageURL(client, id), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}
// DeleteErrorPage will delete the current error page for the load balancer.
func DeleteErrorPage(client *gophercloud.ServiceClient, id int) gophercloud.ErrResult {
var res gophercloud.ErrResult
_, res.Err = client.Delete(errorPageURL(client, id), &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}
// GetStats will retrieve detailed stats related to the load balancer's usage.
func GetStats(client *gophercloud.ServiceClient, id int) StatsResult {
var res StatsResult
_, res.Err = client.Get(statsURL(client, id), &res.Body, nil)
return res
}
// IsContentCached will check to see whether the specified load balancer caches
// content. When content caching is enabled, recently-accessed files are stored
// on the load balancer for easy retrieval by web clients. Content caching
// improves the performance of high traffic web sites by temporarily storing
// data that was recently accessed. While it's cached, requests for that data
// are served by the load balancer, which in turn reduces load off the back-end
// nodes. The result is improved response times for those requests and less
// load on the web server.
func IsContentCached(client *gophercloud.ServiceClient, id int) (bool, error) {
var body interface{}
_, err := client.Get(cacheURL(client, id), &body, nil)
if err != nil {
return false, err
}
var resp struct {
CC struct {
Enabled bool `mapstructure:"enabled"`
} `mapstructure:"contentCaching"`
}
err = mapstructure.Decode(body, &resp)
return resp.CC.Enabled, err
}
func toCachingMap(state bool) map[string]map[string]bool {
return map[string]map[string]bool{
"contentCaching": map[string]bool{"enabled": state},
}
}
// EnableCaching will enable content-caching for the specified load balancer.
func EnableCaching(client *gophercloud.ServiceClient, id int) gophercloud.ErrResult {
var res gophercloud.ErrResult
_, res.Err = client.Put(cacheURL(client, id), toCachingMap(true), nil, nil)
return res
}
// DisableCaching will disable content-caching for the specified load balancer.
func DisableCaching(client *gophercloud.ServiceClient, id int) gophercloud.ErrResult {
var res gophercloud.ErrResult
_, res.Err = client.Put(cacheURL(client, id), toCachingMap(false), nil, nil)
return res
}

View File

@@ -1,420 +0,0 @@
package lbs
import (
"reflect"
"time"
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/lb/v1/acl"
"github.com/rackspace/gophercloud/rackspace/lb/v1/nodes"
"github.com/rackspace/gophercloud/rackspace/lb/v1/sessions"
"github.com/rackspace/gophercloud/rackspace/lb/v1/throttle"
"github.com/rackspace/gophercloud/rackspace/lb/v1/vips"
)
// Protocol represents the network protocol which the load balancer accepts.
type Protocol struct {
// The name of the protocol, e.g. HTTP, LDAP, FTP, etc.
Name string
// The port number for the protocol.
Port int
}
// Algorithm defines how traffic should be directed between back-end nodes.
type Algorithm struct {
// The name of the algorithm, e.g RANDOM, ROUND_ROBIN, etc.
Name string
}
// Status represents the potential state of a load balancer resource.
type Status string
const (
// ACTIVE indicates that the LB is configured properly and ready to serve
// traffic to incoming requests via the configured virtual IPs.
ACTIVE Status = "ACTIVE"
// BUILD indicates that the LB is being provisioned for the first time and
// configuration is being applied to bring the service online. The service
// cannot yet serve incoming requests.
BUILD Status = "BUILD"
// PENDINGUPDATE indicates that the LB is online but configuration changes
// are being applied to update the service based on a previous request.
PENDINGUPDATE Status = "PENDING_UPDATE"
// PENDINGDELETE indicates that the LB is online but configuration changes
// are being applied to begin deletion of the service based on a previous
// request.
PENDINGDELETE Status = "PENDING_DELETE"
// SUSPENDED indicates that the LB has been taken offline and disabled.
SUSPENDED Status = "SUSPENDED"
// ERROR indicates that the system encountered an error when attempting to
// configure the load balancer.
ERROR Status = "ERROR"
// DELETED indicates that the LB has been deleted.
DELETED Status = "DELETED"
)
// Datetime represents the structure of a Created or Updated field.
type Datetime struct {
Time time.Time `mapstructure:"-"`
}
// LoadBalancer represents a load balancer API resource.
type LoadBalancer struct {
// Human-readable name for the load balancer.
Name string
// The unique ID for the load balancer.
ID int
// Represents the service protocol being load balanced. See Protocol type for
// a list of accepted values.
// See http://docs.rackspace.com/loadbalancers/api/v1.0/clb-devguide/content/protocols.html
// for a full list of supported protocols.
Protocol string
// Defines how traffic should be directed between back-end nodes. The default
// algorithm is RANDOM. See Algorithm type for a list of accepted values.
Algorithm string
// The current status of the load balancer.
Status Status
// The number of load balancer nodes.
NodeCount int `mapstructure:"nodeCount"`
// Slice of virtual IPs associated with this load balancer.
VIPs []vips.VIP `mapstructure:"virtualIps"`
// Datetime when the LB was created.
Created Datetime
// Datetime when the LB was created.
Updated Datetime
// Port number for the service you are load balancing.
Port int
// HalfClosed provides the ability for one end of the connection to
// terminate its output while still receiving data from the other end. This
// is only available on TCP/TCP_CLIENT_FIRST protocols.
HalfClosed bool
// Timeout represents the timeout value between a load balancer and its
// nodes. Defaults to 30 seconds with a maximum of 120 seconds.
Timeout int
// The cluster name.
Cluster Cluster
// Nodes shows all the back-end nodes which are associated with the load
// balancer. These are the devices which are delivered traffic.
Nodes []nodes.Node
// Current connection logging configuration.
ConnectionLogging ConnectionLogging
// SessionPersistence specifies whether multiple requests from clients are
// directed to the same node.
SessionPersistence sessions.SessionPersistence
// ConnectionThrottle specifies a limit on the number of connections per IP
// address to help mitigate malicious or abusive traffic to your applications.
ConnectionThrottle throttle.ConnectionThrottle
// The source public and private IP addresses.
SourceAddrs SourceAddrs `mapstructure:"sourceAddresses"`
// Represents the access rules for this particular load balancer. IP addresses
// or subnet ranges, depending on their type (ALLOW or DENY), can be permitted
// or blocked.
AccessList acl.AccessList
}
// SourceAddrs represents the source public and private IP addresses.
type SourceAddrs struct {
IPv4Public string `json:"ipv4Public" mapstructure:"ipv4Public"`
IPv4Private string `json:"ipv4Servicenet" mapstructure:"ipv4Servicenet"`
IPv6Public string `json:"ipv6Public" mapstructure:"ipv6Public"`
IPv6Private string `json:"ipv6Servicenet" mapstructure:"ipv6Servicenet"`
}
// ConnectionLogging - temp
type ConnectionLogging struct {
Enabled bool
}
// Cluster - temp
type Cluster struct {
Name string
}
// LBPage is the page returned by a pager when traversing over a collection of
// LBs.
type LBPage struct {
pagination.LinkedPageBase
}
// IsEmpty checks whether a NetworkPage struct is empty.
func (p LBPage) IsEmpty() (bool, error) {
is, err := ExtractLBs(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
}
// ExtractLBs accepts a Page struct, specifically a LBPage struct, and extracts
// the elements into a slice of LoadBalancer structs. In other words, a generic
// collection is mapped into a relevant slice.
func ExtractLBs(page pagination.Page) ([]LoadBalancer, error) {
var resp struct {
LBs []LoadBalancer `mapstructure:"loadBalancers" json:"loadBalancers"`
}
coll := page.(LBPage).Body
err := mapstructure.Decode(coll, &resp)
s := reflect.ValueOf(coll.(map[string]interface{})["loadBalancers"])
for i := 0; i < s.Len(); i++ {
val := (s.Index(i).Interface()).(map[string]interface{})
ts, err := extractTS(val, "created")
if err != nil {
return resp.LBs, err
}
resp.LBs[i].Created.Time = ts
ts, err = extractTS(val, "updated")
if err != nil {
return resp.LBs, err
}
resp.LBs[i].Updated.Time = ts
}
return resp.LBs, err
}
func extractTS(body map[string]interface{}, key string) (time.Time, error) {
val := body[key].(map[string]interface{})
return time.Parse(time.RFC3339, val["time"].(string))
}
type commonResult struct {
gophercloud.Result
}
// Extract interprets any commonResult as a LB, if possible.
func (r commonResult) Extract() (*LoadBalancer, error) {
if r.Err != nil {
return nil, r.Err
}
var response struct {
LB LoadBalancer `mapstructure:"loadBalancer"`
}
err := mapstructure.Decode(r.Body, &response)
json := r.Body.(map[string]interface{})
lb := json["loadBalancer"].(map[string]interface{})
ts, err := extractTS(lb, "created")
if err != nil {
return nil, err
}
response.LB.Created.Time = ts
ts, err = extractTS(lb, "updated")
if err != nil {
return nil, err
}
response.LB.Updated.Time = ts
return &response.LB, err
}
// CreateResult represents the result of a create operation.
type CreateResult struct {
commonResult
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}
// UpdateResult represents the result of an update operation.
type UpdateResult struct {
gophercloud.ErrResult
}
// GetResult represents the result of a get operation.
type GetResult struct {
commonResult
}
// ProtocolPage is the page returned by a pager when traversing over a
// collection of LB protocols.
type ProtocolPage struct {
pagination.SinglePageBase
}
// IsEmpty checks whether a ProtocolPage struct is empty.
func (p ProtocolPage) IsEmpty() (bool, error) {
is, err := ExtractProtocols(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
}
// ExtractProtocols accepts a Page struct, specifically a ProtocolPage struct,
// and extracts the elements into a slice of Protocol structs. In other
// words, a generic collection is mapped into a relevant slice.
func ExtractProtocols(page pagination.Page) ([]Protocol, error) {
var resp struct {
Protocols []Protocol `mapstructure:"protocols" json:"protocols"`
}
err := mapstructure.Decode(page.(ProtocolPage).Body, &resp)
return resp.Protocols, err
}
// AlgorithmPage is the page returned by a pager when traversing over a
// collection of LB algorithms.
type AlgorithmPage struct {
pagination.SinglePageBase
}
// IsEmpty checks whether an AlgorithmPage struct is empty.
func (p AlgorithmPage) IsEmpty() (bool, error) {
is, err := ExtractAlgorithms(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
}
// ExtractAlgorithms accepts a Page struct, specifically an AlgorithmPage struct,
// and extracts the elements into a slice of Algorithm structs. In other
// words, a generic collection is mapped into a relevant slice.
func ExtractAlgorithms(page pagination.Page) ([]Algorithm, error) {
var resp struct {
Algorithms []Algorithm `mapstructure:"algorithms" json:"algorithms"`
}
err := mapstructure.Decode(page.(AlgorithmPage).Body, &resp)
return resp.Algorithms, err
}
// ErrorPage represents the HTML file that is shown to an end user who is
// attempting to access a load balancer node that is offline/unavailable.
//
// During provisioning, every load balancer is configured with a default error
// page that gets displayed when traffic is requested for an offline node.
//
// You can add a single custom error page with an HTTP-based protocol to a load
// balancer. Page updates override existing content. If a custom error page is
// deleted, or the load balancer is changed to a non-HTTP protocol, the default
// error page is restored.
type ErrorPage struct {
Content string
}
// ErrorPageResult represents the result of an error page operation -
// specifically getting or creating one.
type ErrorPageResult struct {
gophercloud.Result
}
// Extract interprets any commonResult as an ErrorPage, if possible.
func (r ErrorPageResult) Extract() (*ErrorPage, error) {
if r.Err != nil {
return nil, r.Err
}
var response struct {
ErrorPage ErrorPage `mapstructure:"errorpage"`
}
err := mapstructure.Decode(r.Body, &response)
return &response.ErrorPage, err
}
// Stats represents all the key information about a load balancer's usage.
type Stats struct {
// The number of connections closed by this load balancer because its
// ConnectTimeout interval was exceeded.
ConnectTimeout int `mapstructure:"connectTimeOut"`
// The number of transaction or protocol errors for this load balancer.
ConnectError int
// Number of connection failures for this load balancer.
ConnectFailure int
// Number of connections closed by this load balancer because its Timeout
// interval was exceeded.
DataTimedOut int
// Number of connections closed by this load balancer because the
// 'keepalive_timeout' interval was exceeded.
KeepAliveTimedOut int
// The maximum number of simultaneous TCP connections this load balancer has
// processed at any one time.
MaxConnections int `mapstructure:"maxConn"`
// Number of simultaneous connections active at the time of the request.
CurrentConnections int `mapstructure:"currentConn"`
// Number of SSL connections closed by this load balancer because the
// ConnectTimeout interval was exceeded.
SSLConnectTimeout int `mapstructure:"connectTimeOutSsl"`
// Number of SSL transaction or protocol erros in this load balancer.
SSLConnectError int `mapstructure:"connectErrorSsl"`
// Number of SSL connection failures in this load balancer.
SSLConnectFailure int `mapstructure:"connectFailureSsl"`
// Number of SSL connections closed by this load balancer because the
// Timeout interval was exceeded.
SSLDataTimedOut int `mapstructure:"dataTimedOutSsl"`
// Number of SSL connections closed by this load balancer because the
// 'keepalive_timeout' interval was exceeded.
SSLKeepAliveTimedOut int `mapstructure:"keepAliveTimedOutSsl"`
// Maximum number of simultaneous SSL connections this load balancer has
// processed at any one time.
SSLMaxConnections int `mapstructure:"maxConnSsl"`
// Number of simultaneous SSL connections active at the time of the request.
SSLCurrentConnections int `mapstructure:"currentConnSsl"`
}
// StatsResult represents the result of a Stats operation.
type StatsResult struct {
gophercloud.Result
}
// Extract interprets any commonResult as a Stats struct, if possible.
func (r StatsResult) Extract() (*Stats, error) {
if r.Err != nil {
return nil, r.Err
}
res := &Stats{}
err := mapstructure.Decode(r.Body, res)
return res, err
}

View File

@@ -1,49 +0,0 @@
package lbs
import (
"strconv"
"github.com/rackspace/gophercloud"
)
const (
path = "loadbalancers"
protocolsPath = "protocols"
algorithmsPath = "algorithms"
logPath = "connectionlogging"
epPath = "errorpage"
stPath = "stats"
cachePath = "contentcaching"
)
func resourceURL(c *gophercloud.ServiceClient, id int) string {
return c.ServiceURL(path, strconv.Itoa(id))
}
func rootURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL(path)
}
func protocolsURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL(path, protocolsPath)
}
func algorithmsURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL(path, algorithmsPath)
}
func loggingURL(c *gophercloud.ServiceClient, id int) string {
return c.ServiceURL(path, strconv.Itoa(id), logPath)
}
func errorPageURL(c *gophercloud.ServiceClient, id int) string {
return c.ServiceURL(path, strconv.Itoa(id), epPath)
}
func statsURL(c *gophercloud.ServiceClient, id int) string {
return c.ServiceURL(path, strconv.Itoa(id), stPath)
}
func cacheURL(c *gophercloud.ServiceClient, id int) string {
return c.ServiceURL(path, strconv.Itoa(id), cachePath)
}

View File

@@ -1,21 +0,0 @@
/*
Package monitors provides information and interaction with the Health Monitor
API resource for the Rackspace Cloud Load Balancer service.
The load balancing service includes a health monitoring resource that
periodically checks your back-end nodes to ensure they are responding correctly.
If a node does not respond, it is removed from rotation until the health monitor
determines that the node is functional. In addition to being performed
periodically, a health check also executes against every new node that is
added, to ensure that the node is operating properly before allowing it to
service traffic. Only one health monitor is allowed to be enabled on a load
balancer at a time.
As part of a good strategy for monitoring connections, secondary nodes should
also be created which provide failover for effectively routing traffic in case
the primary node fails. This is an additional feature that ensures that you
remain up in case your primary node fails.
There are three types of health monitor: CONNECT, HTTP and HTTPS.
*/
package monitors

View File

@@ -1,87 +0,0 @@
package monitors
import (
"fmt"
"net/http"
"strconv"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func _rootURL(lbID int) string {
return "/loadbalancers/" + strconv.Itoa(lbID) + "/healthmonitor"
}
func mockGetResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"healthMonitor": {
"type": "CONNECT",
"delay": 10,
"timeout": 10,
"attemptsBeforeDeactivation": 3
}
}
`)
})
}
func mockUpdateConnectResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"healthMonitor": {
"type": "CONNECT",
"delay": 10,
"timeout": 10,
"attemptsBeforeDeactivation": 3
}
}
`)
w.WriteHeader(http.StatusAccepted)
})
}
func mockUpdateHTTPResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"healthMonitor": {
"attemptsBeforeDeactivation": 3,
"bodyRegex": "{regex}",
"delay": 10,
"path": "/foo",
"statusRegex": "200",
"timeout": 10,
"type": "HTTPS"
}
}
`)
w.WriteHeader(http.StatusAccepted)
})
}
func mockDeleteResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusAccepted)
})
}

View File

@@ -1,160 +0,0 @@
package monitors
import (
"errors"
"github.com/rackspace/gophercloud"
)
var (
errAttemptLimit = errors.New("AttemptLimit field must be an int greater than 1 and less than 10")
errDelay = errors.New("Delay field must be an int greater than 1 and less than 10")
errTimeout = errors.New("Timeout field must be an int greater than 1 and less than 10")
)
// UpdateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Update operation in this package.
type UpdateOptsBuilder interface {
ToMonitorUpdateMap() (map[string]interface{}, error)
}
// UpdateConnectMonitorOpts represents the options needed to update a CONNECT
// monitor.
type UpdateConnectMonitorOpts struct {
// Required - number of permissible monitor failures before removing a node
// from rotation. Must be a number between 1 and 10.
AttemptLimit int
// Required - the minimum number of seconds to wait before executing the
// health monitor. Must be a number between 1 and 3600.
Delay int
// Required - maximum number of seconds to wait for a connection to be
// established before timing out. Must be a number between 1 and 300.
Timeout int
}
// ToMonitorUpdateMap produces a map for updating CONNECT monitors.
func (opts UpdateConnectMonitorOpts) ToMonitorUpdateMap() (map[string]interface{}, error) {
type m map[string]interface{}
if !gophercloud.IntWithinRange(opts.AttemptLimit, 1, 10) {
return m{}, errAttemptLimit
}
if !gophercloud.IntWithinRange(opts.Delay, 1, 3600) {
return m{}, errDelay
}
if !gophercloud.IntWithinRange(opts.Timeout, 1, 300) {
return m{}, errTimeout
}
return m{"healthMonitor": m{
"attemptsBeforeDeactivation": opts.AttemptLimit,
"delay": opts.Delay,
"timeout": opts.Timeout,
"type": CONNECT,
}}, nil
}
// UpdateHTTPMonitorOpts represents the options needed to update a HTTP monitor.
type UpdateHTTPMonitorOpts struct {
// Required - number of permissible monitor failures before removing a node
// from rotation. Must be a number between 1 and 10.
AttemptLimit int `mapstructure:"attemptsBeforeDeactivation"`
// Required - the minimum number of seconds to wait before executing the
// health monitor. Must be a number between 1 and 3600.
Delay int
// Required - maximum number of seconds to wait for a connection to be
// established before timing out. Must be a number between 1 and 300.
Timeout int
// Required - a regular expression that will be used to evaluate the contents
// of the body of the response.
BodyRegex string
// Required - the HTTP path that will be used in the sample request.
Path string
// Required - a regular expression that will be used to evaluate the HTTP
// status code returned in the response.
StatusRegex string
// Optional - the name of a host for which the health monitors will check.
HostHeader string
// Required - either HTTP or HTTPS
Type Type
}
// ToMonitorUpdateMap produces a map for updating HTTP(S) monitors.
func (opts UpdateHTTPMonitorOpts) ToMonitorUpdateMap() (map[string]interface{}, error) {
type m map[string]interface{}
if !gophercloud.IntWithinRange(opts.AttemptLimit, 1, 10) {
return m{}, errAttemptLimit
}
if !gophercloud.IntWithinRange(opts.Delay, 1, 3600) {
return m{}, errDelay
}
if !gophercloud.IntWithinRange(opts.Timeout, 1, 300) {
return m{}, errTimeout
}
if opts.Type != HTTP && opts.Type != HTTPS {
return m{}, errors.New("Type must either by HTTP or HTTPS")
}
if opts.BodyRegex == "" {
return m{}, errors.New("BodyRegex is a required field")
}
if opts.Path == "" {
return m{}, errors.New("Path is a required field")
}
if opts.StatusRegex == "" {
return m{}, errors.New("StatusRegex is a required field")
}
json := m{
"attemptsBeforeDeactivation": opts.AttemptLimit,
"delay": opts.Delay,
"timeout": opts.Timeout,
"type": opts.Type,
"bodyRegex": opts.BodyRegex,
"path": opts.Path,
"statusRegex": opts.StatusRegex,
}
if opts.HostHeader != "" {
json["hostHeader"] = opts.HostHeader
}
return m{"healthMonitor": json}, nil
}
// Update is the operation responsible for updating a health monitor.
func Update(c *gophercloud.ServiceClient, id int, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToMonitorUpdateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = c.Put(rootURL(c, id), reqBody, nil, nil)
return res
}
// Get is the operation responsible for showing details of a health monitor.
func Get(c *gophercloud.ServiceClient, id int) GetResult {
var res GetResult
_, res.Err = c.Get(rootURL(c, id), &res.Body, nil)
return res
}
// Delete is the operation responsible for deleting a health monitor.
func Delete(c *gophercloud.ServiceClient, id int) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(rootURL(c, id), nil)
return res
}

View File

@@ -1,90 +0,0 @@
package monitors
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
)
// Type represents the type of Monitor.
type Type string
// Useful constants.
const (
CONNECT Type = "CONNECT"
HTTP Type = "HTTP"
HTTPS Type = "HTTPS"
)
// Monitor represents a health monitor API resource. A monitor comes in three
// forms: CONNECT, HTTP or HTTPS.
//
// A CONNECT monitor establishes a basic connection to each node on its defined
// port to ensure that the service is listening properly. The connect monitor
// is the most basic type of health check and does no post-processing or
// protocol-specific health checks.
//
// HTTP and HTTPS health monitors are generally considered more intelligent and
// powerful than CONNECT. It is capable of processing an HTTP or HTTPS response
// to determine the condition of a node. It supports the same basic properties
// as CONNECT and includes additional attributes that are used to evaluate the
// HTTP response.
type Monitor struct {
// Number of permissible monitor failures before removing a node from
// rotation.
AttemptLimit int `mapstructure:"attemptsBeforeDeactivation"`
// The minimum number of seconds to wait before executing the health monitor.
Delay int
// Maximum number of seconds to wait for a connection to be established
// before timing out.
Timeout int
// Type of the health monitor.
Type Type
// A regular expression that will be used to evaluate the contents of the
// body of the response.
BodyRegex string
// The name of a host for which the health monitors will check.
HostHeader string
// The HTTP path that will be used in the sample request.
Path string
// A regular expression that will be used to evaluate the HTTP status code
// returned in the response.
StatusRegex string
}
// UpdateResult represents the result of an Update operation.
type UpdateResult struct {
gophercloud.ErrResult
}
// GetResult represents the result of a Get operation.
type GetResult struct {
gophercloud.Result
}
// DeleteResult represents the result of an Delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}
// Extract interprets any GetResult as a Monitor.
func (r GetResult) Extract() (*Monitor, error) {
if r.Err != nil {
return nil, r.Err
}
var response struct {
M Monitor `mapstructure:"healthMonitor"`
}
err := mapstructure.Decode(r.Body, &response)
return &response.M, err
}

View File

@@ -1,16 +0,0 @@
package monitors
import (
"strconv"
"github.com/rackspace/gophercloud"
)
const (
path = "loadbalancers"
monitorPath = "healthmonitor"
)
func rootURL(c *gophercloud.ServiceClient, lbID int) string {
return c.ServiceURL(path, strconv.Itoa(lbID), monitorPath)
}

View File

@@ -1,35 +0,0 @@
/*
Package nodes provides information and interaction with the Node API resource
for the Rackspace Cloud Load Balancer service.
Nodes are responsible for servicing the requests received through the load
balancer's virtual IP. A node is usually a virtual machine. By default, the
load balancer employs a basic health check that ensures the node is listening
on its defined port. The node is checked at the time of addition and at regular
intervals as defined by the load balancer's health check configuration. If a
back-end node is not listening on its port, or does not meet the conditions of
the defined check, then connections will not be forwarded to the node, and its
status is changed to OFFLINE. Only nodes that are in an ONLINE status receive
and can service traffic from the load balancer.
All nodes have an associated status that indicates whether the node is
ONLINE, OFFLINE, or DRAINING. Only nodes that are in ONLINE status can receive
and service traffic from the load balancer. The OFFLINE status represents a
node that cannot accept or service traffic. A node in DRAINING status
represents a node that stops the traffic manager from sending any additional
new connections to the node, but honors established sessions. If the traffic
manager receives a request and session persistence requires that the node is
used, the traffic manager uses it. The status is determined by the passive or
active health monitors.
If the WEIGHTED_ROUND_ROBIN load balancer algorithm mode is selected, then the
caller should assign the relevant weights to the node as part of the weight
attribute of the node element. When the algorithm of the load balancer is
changed to WEIGHTED_ROUND_ROBIN and the nodes do not already have an assigned
weight, the service automatically sets the weight to 1 for all nodes.
One or more secondary nodes can be added to a specified load balancer so that
if all the primary nodes fail, traffic can be redirected to secondary nodes.
The type attribute allows configuring the node as either PRIMARY or SECONDARY.
*/
package nodes

View File

@@ -1,243 +0,0 @@
package nodes
import (
"fmt"
"net/http"
"strconv"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func _rootURL(lbID int) string {
return "/loadbalancers/" + strconv.Itoa(lbID) + "/nodes"
}
func _nodeURL(lbID, nodeID int) string {
return _rootURL(lbID) + "/" + strconv.Itoa(nodeID)
}
func mockListResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"nodes": [
{
"id": 410,
"address": "10.1.1.1",
"port": 80,
"condition": "ENABLED",
"status": "ONLINE",
"weight": 3,
"type": "PRIMARY"
},
{
"id": 411,
"address": "10.1.1.2",
"port": 80,
"condition": "ENABLED",
"status": "ONLINE",
"weight": 8,
"type": "SECONDARY"
}
]
}
`)
})
}
func mockCreateResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"nodes": [
{
"address": "10.2.2.3",
"port": 80,
"condition": "ENABLED",
"type": "PRIMARY"
},
{
"address": "10.2.2.4",
"port": 81,
"condition": "ENABLED",
"type": "SECONDARY"
}
]
}
`)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted)
fmt.Fprintf(w, `
{
"nodes": [
{
"address": "10.2.2.3",
"id": 185,
"port": 80,
"status": "ONLINE",
"condition": "ENABLED",
"weight": 1,
"type": "PRIMARY"
},
{
"address": "10.2.2.4",
"id": 186,
"port": 81,
"status": "ONLINE",
"condition": "ENABLED",
"weight": 1,
"type": "SECONDARY"
}
]
}
`)
})
}
func mockCreateErrResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"nodes": [
{
"address": "10.2.2.3",
"port": 80,
"condition": "ENABLED",
"type": "PRIMARY"
},
{
"address": "10.2.2.4",
"port": 81,
"condition": "ENABLED",
"type": "SECONDARY"
}
]
}
`)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(422) // Unprocessable Entity
fmt.Fprintf(w, `
{
"code": 422,
"message": "Load Balancer '%d' has a status of 'PENDING_UPDATE' and is considered immutable."
}
`, lbID)
})
}
func mockBatchDeleteResponse(t *testing.T, lbID int, ids []int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
r.ParseForm()
for k, v := range ids {
fids := r.Form["id"]
th.AssertEquals(t, strconv.Itoa(v), fids[k])
}
w.WriteHeader(http.StatusAccepted)
})
}
func mockDeleteResponse(t *testing.T, lbID, nodeID int) {
th.Mux.HandleFunc(_nodeURL(lbID, nodeID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusAccepted)
})
}
func mockGetResponse(t *testing.T, lbID, nodeID int) {
th.Mux.HandleFunc(_nodeURL(lbID, nodeID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"node": {
"id": 410,
"address": "10.1.1.1",
"port": 80,
"condition": "ENABLED",
"status": "ONLINE",
"weight": 12,
"type": "PRIMARY"
}
}
`)
})
}
func mockUpdateResponse(t *testing.T, lbID, nodeID int) {
th.Mux.HandleFunc(_nodeURL(lbID, nodeID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"node": {
"condition": "DRAINING",
"weight": 10,
"type": "SECONDARY"
}
}
`)
w.WriteHeader(http.StatusAccepted)
})
}
func mockListEventsResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID)+"/events", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"nodeServiceEvents": [
{
"detailedMessage": "Node is ok",
"nodeId": 373,
"id": 7,
"type": "UPDATE_NODE",
"description": "Node '373' status changed to 'ONLINE' for load balancer '323'",
"category": "UPDATE",
"severity": "INFO",
"relativeUri": "/406271/loadbalancers/323/nodes/373/events",
"accountId": 406271,
"loadbalancerId": 323,
"title": "Node Status Updated",
"author": "Rackspace Cloud",
"created": "10-30-2012 10:18:23"
}
]
}
`)
})
}

View File

@@ -1,286 +0,0 @@
package nodes
import (
"errors"
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// List is the operation responsible for returning a paginated collection of
// load balancer nodes. It requires the node ID, its parent load balancer ID,
// and optional limit integer (passed in either as a pointer or a nil poitner).
func List(client *gophercloud.ServiceClient, loadBalancerID int, limit *int) pagination.Pager {
url := rootURL(client, loadBalancerID)
if limit != nil {
url += fmt.Sprintf("?limit=%d", limit)
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return NodePage{pagination.SinglePageBase(r)}
})
}
// CreateOptsBuilder is the interface responsible for generating the JSON
// for a Create operation.
type CreateOptsBuilder interface {
ToNodeCreateMap() (map[string]interface{}, error)
}
// CreateOpts is a slice of CreateOpt structs, that allow the user to create
// multiple nodes in a single operation (one node per CreateOpt).
type CreateOpts []CreateOpt
// CreateOpt represents the options to create a single node.
type CreateOpt struct {
// Required - the IP address or CIDR for this back-end node. It can either be
// a private IP (ServiceNet) or a public IP.
Address string
// Optional - the port on which traffic is sent and received.
Port int
// Optional - the condition of the node. See the consts in Results.go.
Condition Condition
// Optional - the type of the node. See the consts in Results.go.
Type Type
// Optional - a pointer to an integer between 0 and 100.
Weight *int
}
func validateWeight(weight *int) error {
if weight != nil && (*weight > 100 || *weight < 0) {
return errors.New("Weight must be a valid int between 0 and 100")
}
return nil
}
// ToNodeCreateMap converts a slice of options into a map that can be used for
// the JSON.
func (opts CreateOpts) ToNodeCreateMap() (map[string]interface{}, error) {
type nodeMap map[string]interface{}
nodes := []nodeMap{}
for k, v := range opts {
if v.Address == "" {
return nodeMap{}, fmt.Errorf("ID is a required attribute, none provided for %d CreateOpt element", k)
}
if weightErr := validateWeight(v.Weight); weightErr != nil {
return nodeMap{}, weightErr
}
node := make(map[string]interface{})
node["address"] = v.Address
if v.Port > 0 {
node["port"] = v.Port
}
if v.Condition != "" {
node["condition"] = v.Condition
}
if v.Type != "" {
node["type"] = v.Type
}
if v.Weight != nil {
node["weight"] = &v.Weight
}
nodes = append(nodes, node)
}
return nodeMap{"nodes": nodes}, nil
}
// Create is the operation responsible for creating a new node on a load
// balancer. Since every load balancer exists in both ServiceNet and the public
// Internet, both private and public IP addresses can be used for nodes.
//
// If nodes need time to boot up services before they become operational, you
// can temporarily prevent traffic from being sent to that node by setting the
// Condition field to DRAINING. Health checks will still be performed; but once
// your node is ready, you can update its condition to ENABLED and have it
// handle traffic.
func Create(client *gophercloud.ServiceClient, loadBalancerID int, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToNodeCreateMap()
if err != nil {
res.Err = err
return res
}
resp, err := client.Post(rootURL(client, loadBalancerID), reqBody, &res.Body, nil)
if err != nil {
res.Err = err
return res
}
pr := pagination.PageResultFromParsed(resp, res.Body)
return CreateResult{pagination.SinglePageBase(pr)}
}
// BulkDelete is the operation responsible for batch deleting multiple nodes in
// a single operation. It accepts a slice of integer IDs and will remove them
// from the load balancer. The maximum limit is 10 node removals at once.
func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, nodeIDs []int) DeleteResult {
var res DeleteResult
if len(nodeIDs) > 10 || len(nodeIDs) == 0 {
res.Err = errors.New("You must provide a minimum of 1 and a maximum of 10 node IDs")
return res
}
url := rootURL(c, loadBalancerID)
url += gophercloud.IDSliceToQueryString("id", nodeIDs)
_, res.Err = c.Delete(url, nil)
return res
}
// Get is the operation responsible for showing details for a single node.
func Get(c *gophercloud.ServiceClient, lbID, nodeID int) GetResult {
var res GetResult
_, res.Err = c.Get(resourceURL(c, lbID, nodeID), &res.Body, nil)
return res
}
// UpdateOptsBuilder represents a type that can be converted into a JSON-like
// map structure.
type UpdateOptsBuilder interface {
ToNodeUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts represent the options for updating an existing node.
type UpdateOpts struct {
// Optional - the condition of the node. See the consts in Results.go.
Condition Condition
// Optional - the type of the node. See the consts in Results.go.
Type Type
// Optional - a pointer to an integer between 0 and 100.
Weight *int
}
// ToNodeUpdateMap converts an options struct into a JSON-like map.
func (opts UpdateOpts) ToNodeUpdateMap() (map[string]interface{}, error) {
node := make(map[string]interface{})
if opts.Condition != "" {
node["condition"] = opts.Condition
}
if opts.Weight != nil {
if weightErr := validateWeight(opts.Weight); weightErr != nil {
return node, weightErr
}
node["weight"] = &opts.Weight
}
if opts.Type != "" {
node["type"] = opts.Type
}
return map[string]interface{}{"node": node}, nil
}
// Update is the operation responsible for updating an existing node. A node's
// IP, port, and status are immutable attributes and cannot be modified.
func Update(c *gophercloud.ServiceClient, lbID, nodeID int, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToNodeUpdateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = c.Put(resourceURL(c, lbID, nodeID), reqBody, nil, nil)
return res
}
// Delete is the operation responsible for permanently deleting a node.
func Delete(c *gophercloud.ServiceClient, lbID, nodeID int) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(resourceURL(c, lbID, nodeID), nil)
return res
}
// ListEventsOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListEventsOptsBuilder interface {
ToEventsListQuery() (string, error)
}
// ListEventsOpts allows the filtering and sorting of paginated collections through
// the API.
type ListEventsOpts struct {
Marker string `q:"marker"`
Limit int `q:"limit"`
}
// ToEventsListQuery formats a ListOpts into a query string.
func (opts ListEventsOpts) ToEventsListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// ListEvents is the operation responsible for listing all the events
// associated with the activity between the node and the load balancer. The
// events report errors found with the node. The detailedMessage provides the
// detailed reason for the error.
func ListEvents(client *gophercloud.ServiceClient, loadBalancerID int, opts ListEventsOptsBuilder) pagination.Pager {
url := eventsURL(client, loadBalancerID)
if opts != nil {
query, err := opts.ToEventsListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return NodeEventPage{pagination.SinglePageBase(r)}
})
}
// GetByIPPort locates a load balancer node by IP and port.
func GetByIPPort(
client *gophercloud.ServiceClient,
loadBalancerID int,
address string,
port int,
) (*Node, error) {
// nil until found
var found *Node
List(client, loadBalancerID, nil).EachPage(func(page pagination.Page) (bool, error) {
lbNodes, err := ExtractNodes(page)
if err != nil {
return false, err
}
for _, trialNode := range lbNodes {
if trialNode.Address == address && trialNode.Port == port {
found = &trialNode
return false, nil
}
}
return true, nil
})
if found == nil {
return nil, errors.New("Unable to get node by IP and Port")
}
return found, nil
}

View File

@@ -1,213 +0,0 @@
package nodes
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// Node represents a back-end device, usually a virtual machine, that can
// handle traffic. It is assigned traffic based on its parent load balancer.
type Node struct {
// The IP address or CIDR for this back-end node.
Address string
// The unique ID for this node.
ID int
// The port on which traffic is sent and received.
Port int
// The node's status.
Status Status
// The node's condition.
Condition Condition
// The priority at which this node will receive traffic if a weighted
// algorithm is used by its parent load balancer. Ranges from 1 to 100.
Weight int
// Type of node.
Type Type
}
// Type indicates whether the node is of a PRIMARY or SECONDARY nature.
type Type string
const (
// PRIMARY nodes are in the normal rotation to receive traffic from the load
// balancer.
PRIMARY Type = "PRIMARY"
// SECONDARY nodes are only in the rotation to receive traffic from the load
// balancer when all the primary nodes fail. This provides a failover feature
// that automatically routes traffic to the secondary node in the event that
// the primary node is disabled or in a failing state. Note that active
// health monitoring must be enabled on the load balancer to enable the
// failover feature to the secondary node.
SECONDARY Type = "SECONDARY"
)
// Condition represents the condition of a node.
type Condition string
const (
// ENABLED indicates that the node is permitted to accept new connections.
ENABLED Condition = "ENABLED"
// DISABLED indicates that the node is not permitted to accept any new
// connections regardless of session persistence configuration. Existing
// connections are forcibly terminated.
DISABLED Condition = "DISABLED"
// DRAINING indicates that the node is allowed to service existing
// established connections and connections that are being directed to it as a
// result of the session persistence configuration.
DRAINING Condition = "DRAINING"
)
// Status indicates whether the node can accept service traffic. If a node is
// not listening on its port or does not meet the conditions of the defined
// active health check for the load balancer, then the load balancer does not
// forward connections, and its status is listed as OFFLINE.
type Status string
const (
// ONLINE indicates that the node is healthy and capable of receiving traffic
// from the load balancer.
ONLINE Status = "ONLINE"
// OFFLINE indicates that the node is not in a position to receive service
// traffic. It is usually switched into this state when a health check is not
// satisfied with the node's response time.
OFFLINE Status = "OFFLINE"
)
// NodePage is the page returned by a pager when traversing over a collection
// of nodes.
type NodePage struct {
pagination.SinglePageBase
}
// IsEmpty checks whether a NodePage struct is empty.
func (p NodePage) IsEmpty() (bool, error) {
is, err := ExtractNodes(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
}
func commonExtractNodes(body interface{}) ([]Node, error) {
var resp struct {
Nodes []Node `mapstructure:"nodes" json:"nodes"`
}
err := mapstructure.Decode(body, &resp)
return resp.Nodes, err
}
// ExtractNodes accepts a Page struct, specifically a NodePage struct, and
// extracts the elements into a slice of Node structs. In other words, a
// generic collection is mapped into a relevant slice.
func ExtractNodes(page pagination.Page) ([]Node, error) {
return commonExtractNodes(page.(NodePage).Body)
}
// CreateResult represents the result of a create operation. Since multiple
// nodes can be added in one operation, this result represents multiple nodes
// and should be treated as a typical pagination Page. Use its ExtractNodes
// method to get out a slice of Node structs.
type CreateResult struct {
pagination.SinglePageBase
}
// ExtractNodes extracts a slice of Node structs from a CreateResult.
func (res CreateResult) ExtractNodes() ([]Node, error) {
if res.Err != nil {
return nil, res.Err
}
return commonExtractNodes(res.Body)
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}
type commonResult struct {
gophercloud.Result
}
// GetResult represents the result of a get operation.
type GetResult struct {
commonResult
}
// UpdateResult represents the result of an update operation.
type UpdateResult struct {
gophercloud.ErrResult
}
func (r commonResult) Extract() (*Node, error) {
if r.Err != nil {
return nil, r.Err
}
var response struct {
Node Node `mapstructure:"node"`
}
err := mapstructure.Decode(r.Body, &response)
return &response.Node, err
}
// NodeEvent represents a service event that occurred between a node and a
// load balancer.
type NodeEvent struct {
ID int
DetailedMessage string
NodeID int
Type string
Description string
Category string
Severity string
RelativeURI string
AccountID int
LoadBalancerID int
Title string
Author string
Created string
}
// NodeEventPage is a concrete type which embeds the common SinglePageBase
// struct, and is used when traversing node event collections.
type NodeEventPage struct {
pagination.SinglePageBase
}
// IsEmpty is a concrete function which indicates whether an NodeEventPage is
// empty or not.
func (r NodeEventPage) IsEmpty() (bool, error) {
is, err := ExtractNodeEvents(r)
if err != nil {
return true, err
}
return len(is) == 0, nil
}
// ExtractNodeEvents accepts a Page struct, specifically a NodeEventPage
// struct, and extracts the elements into a slice of NodeEvent structs. In
// other words, the collection is mapped into a relevant slice.
func ExtractNodeEvents(page pagination.Page) ([]NodeEvent, error) {
var resp struct {
Events []NodeEvent `mapstructure:"nodeServiceEvents" json:"nodeServiceEvents"`
}
err := mapstructure.Decode(page.(NodeEventPage).Body, &resp)
return resp.Events, err
}

View File

@@ -1,25 +0,0 @@
package nodes
import (
"strconv"
"github.com/rackspace/gophercloud"
)
const (
lbPath = "loadbalancers"
nodePath = "nodes"
eventPath = "events"
)
func resourceURL(c *gophercloud.ServiceClient, lbID, nodeID int) string {
return c.ServiceURL(lbPath, strconv.Itoa(lbID), nodePath, strconv.Itoa(nodeID))
}
func rootURL(c *gophercloud.ServiceClient, lbID int) string {
return c.ServiceURL(lbPath, strconv.Itoa(lbID), nodePath)
}
func eventsURL(c *gophercloud.ServiceClient, lbID int) string {
return c.ServiceURL(lbPath, strconv.Itoa(lbID), nodePath, eventPath)
}

View File

@@ -1,30 +0,0 @@
/*
Package sessions provides information and interaction with the Session
Persistence feature of the Rackspace Cloud Load Balancer service.
Session persistence is a feature of the load balancing service that forces
multiple requests from clients (of the same protocol) to be directed to the
same node. This is common with many web applications that do not inherently
share application state between back-end servers.
There are two modes to choose from: HTTP_COOKIE and SOURCE_IP. You can only set
one of the session persistence modes on a load balancer, and it can only
support one protocol. If you set HTTP_COOKIE mode for an HTTP load balancer, it
supports session persistence for HTTP requests only. Likewise, if you set
SOURCE_IP mode for an HTTPS load balancer, it supports session persistence for
only HTTPS requests.
To support session persistence for both HTTP and HTTPS requests concurrently,
choose one of the following options:
- Use two load balancers, one configured for session persistence for HTTP
requests and the other configured for session persistence for HTTPS requests.
That way, the load balancers support session persistence for both HTTP and
HTTPS requests concurrently, with each load balancer supporting one of the
protocols.
- Use one load balancer, configure it for session persistence for HTTP requests,
and then enable SSL termination for that load balancer. The load balancer
supports session persistence for both HTTP and HTTPS requests concurrently.
*/
package sessions

View File

@@ -1,59 +0,0 @@
package sessions
import (
"fmt"
"net/http"
"strconv"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func _rootURL(id int) string {
return "/loadbalancers/" + strconv.Itoa(id) + "/sessionpersistence"
}
func mockGetResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"sessionPersistence": {
"persistenceType": "HTTP_COOKIE"
}
}
`)
})
}
func mockEnableResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"sessionPersistence": {
"persistenceType": "HTTP_COOKIE"
}
}
`)
w.WriteHeader(http.StatusAccepted)
fmt.Fprintf(w, `{}`)
})
}
func mockDisableResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusAccepted)
})
}

View File

@@ -1,63 +0,0 @@
package sessions
import (
"errors"
"github.com/rackspace/gophercloud"
)
// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package.
type CreateOptsBuilder interface {
ToSPCreateMap() (map[string]interface{}, error)
}
// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts struct {
// Required - can either be HTTPCOOKIE or SOURCEIP
Type Type
}
// ToSPCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToSPCreateMap() (map[string]interface{}, error) {
sp := make(map[string]interface{})
if opts.Type == "" {
return sp, errors.New("Type is a required field")
}
sp["persistenceType"] = opts.Type
return map[string]interface{}{"sessionPersistence": sp}, nil
}
// Enable is the operation responsible for enabling session persistence for a
// particular load balancer.
func Enable(c *gophercloud.ServiceClient, lbID int, opts CreateOptsBuilder) EnableResult {
var res EnableResult
reqBody, err := opts.ToSPCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = c.Put(rootURL(c, lbID), reqBody, &res.Body, nil)
return res
}
// Get is the operation responsible for showing details of the session
// persistence configuration for a particular load balancer.
func Get(c *gophercloud.ServiceClient, lbID int) GetResult {
var res GetResult
_, res.Err = c.Get(rootURL(c, lbID), &res.Body, nil)
return res
}
// Disable is the operation responsible for disabling session persistence for a
// particular load balancer.
func Disable(c *gophercloud.ServiceClient, lbID int) DisableResult {
var res DisableResult
_, res.Err = c.Delete(rootURL(c, lbID), nil)
return res
}

View File

@@ -1,58 +0,0 @@
package sessions
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
)
// Type represents the type of session persistence being used.
type Type string
const (
// HTTPCOOKIE is a session persistence mechanism that inserts an HTTP cookie
// and is used to determine the destination back-end node. This is supported
// for HTTP load balancing only.
HTTPCOOKIE Type = "HTTP_COOKIE"
// SOURCEIP is a session persistence mechanism that keeps track of the source
// IP address that is mapped and is able to determine the destination
// back-end node. This is supported for HTTPS pass-through and non-HTTP load
// balancing only.
SOURCEIP Type = "SOURCE_IP"
)
// SessionPersistence indicates how a load balancer is using session persistence
type SessionPersistence struct {
Type Type `mapstructure:"persistenceType"`
}
// EnableResult represents the result of an enable operation.
type EnableResult struct {
gophercloud.ErrResult
}
// DisableResult represents the result of a disable operation.
type DisableResult struct {
gophercloud.ErrResult
}
// GetResult represents the result of a get operation.
type GetResult struct {
gophercloud.Result
}
// Extract interprets a GetResult as an SP, if possible.
func (r GetResult) Extract() (*SessionPersistence, error) {
if r.Err != nil {
return nil, r.Err
}
var response struct {
SP SessionPersistence `mapstructure:"sessionPersistence"`
}
err := mapstructure.Decode(r.Body, &response)
return &response.SP, err
}

View File

@@ -1,16 +0,0 @@
package sessions
import (
"strconv"
"github.com/rackspace/gophercloud"
)
const (
path = "loadbalancers"
spPath = "sessionpersistence"
)
func rootURL(c *gophercloud.ServiceClient, id int) string {
return c.ServiceURL(path, strconv.Itoa(id), spPath)
}

View File

@@ -1,22 +0,0 @@
/*
Package ssl provides information and interaction with the SSL Termination
feature of the Rackspace Cloud Load Balancer service.
You may only enable and configure SSL termination on load balancers with
non-secure protocols, such as HTTP, but not HTTPS.
SSL-terminated load balancers decrypt the traffic at the traffic manager and
pass unencrypted traffic to the back-end node. Because of this, the customer's
back-end nodes don't know what protocol the client requested. For this reason,
the X-Forwarded-Proto (XFP) header has been added for identifying the
originating protocol of an HTTP request as "http" or "https" depending on what
protocol the client requested.
Not every service returns certificates in the proper order. Please verify that
your chain of certificates matches that of walking up the chain from the domain
to the CA root.
If used for HTTP to HTTPS redirection, the LoadBalancer's securePort attribute
must be set to 443, and its secureTrafficOnly attribute must be true.
*/
package ssl

View File

@@ -1,196 +0,0 @@
package ssl
import (
"fmt"
"net/http"
"strconv"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func _rootURL(id int) string {
return "/loadbalancers/" + strconv.Itoa(id) + "/ssltermination"
}
func _certURL(id, certID int) string {
url := _rootURL(id) + "/certificatemappings"
if certID > 0 {
url += "/" + strconv.Itoa(certID)
}
return url
}
func mockGetResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"sslTermination": {
"certificate": "-----BEGIN CERTIFICATE-----\nMIIEXTCCA0WgAwIBAgIGATTEAjK3MA0GCSqGSIb3DQEBBQUAMIGDMRkwFwYDVQQD\nExBUZXN0IENBIFNUdWIgS2V5MRcwFQYDVQQLEw5QbGF0Zm9ybSBMYmFhczEaMBgG\nA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFDASBgNVBAcTC1NhbiBBbnRvbmlvMQ4w\nDAYDVQQIEwVUZXhhczELMAkGA1UEBhMCVVMwHhcNMTIwMTA5MTk0NjQ1WhcNMTQw\nMTA4MTk0NjQ1WjCBgjELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMRQwEgYD\nVQQHEwtTYW4gQW50b25pbzEaMBgGA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFzAV\nBgNVBAsTDlBsYXRmb3JtIExiYWFzMRgwFgYDVQQDEw9UZXN0IENsaWVudCBLZXkw\nggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAi51IylFnHtNLT8C0NVfc\nOBfAsP2D5es1qhrOWHCGlgAuDMksBsCc7FPo5PSBOmQ+6z8HtCFbrLoC5/Zx0F5b\nfVegjA+xKjI2HGASsYHHM0BFEH2UjUcJrWiMWtxQuW6Phbqulo7JwjmygMEmIkeK\nf+FtkE9mrq+E8K40/thrjxl3/ZcJD1+3dcp+ZuzVJ2t1E4iGKCx79IZFsysKiuf+\n+E0i6iGvvI6UcbcZxVxQj2TplJkFuoX5kDgClIX9Dr9y6aJ4SCh+GRhvHl+DTaz0\nnCvghachHZtIeztRDqxWApjOOzs93dSelrviMXDr8fqyEAGg7YIhgui0aZBsWCen\nAgMBAAGjgdUwgdIwgbAGA1UdIwSBqDCBpYAUNpx1Pc6cGA7KqEwHMmHBTZMA7lSh\ngYmkgYYwgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVU4IBATAd\nBgNVHQ4EFgQULueOfsjZZOHwJHZwBy6u0swnpccwDQYJKoZIhvcNAQEFBQADggEB\nAFNuqSVUaotUJoWDv4z7Kbi6JFpTjDht5ORw4BdVYlRD4h9DACAFzPrPV2ym/Osp\nhNMdZq6msZku7MdOSQVhdeGWrSNk3M8O9Hg7cVzPNXOF3iNoo3irQ5tURut44xs4\nWw5YWQqS9WyUY5snD8tm7Y1rQTPfhg+678xIq/zWCv/u+FSnfVv1nlhLVQkEeG/Y\ngh1uMaTIpUKTGEjIAGtpGP7wwIcXptR/HyfzhTUSTaWc1Ef7zoKT9LL5z3IV1hC2\njVWz+RwYs98LjMuksJFoHqRfWyYhCIym0jb6GTwaEmpxAjc+d7OLNQdnoEGoUYGP\nYjtfkRYg265ESMA+Kww4Xy8=\n-----END CERTIFICATE-----\n",
"enabled": true,
"secureTrafficOnly": false,
"intermediateCertificate": "-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzEZMBcGA1UEAxMQVGVz\ndCBDQSBTVHViIEtleTEXMBUGA1UECxMOUGxhdGZvcm0gTGJhYXMxGjAYBgNVBAoT\nEVJhY2tzcGFjZSBIb3N0aW5nMRQwEgYDVQQHEwtTYW4gQW50b25pbzEOMAwGA1UE\nCBMFVGV4YXMxCzAJBgNVBAYTAlVTMB4XDTEyMDEwOTE5NDU0OVoXDTE0MDEwODE5\nNDU0OVowgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVUzCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNh55lwTVwQvNoEZjq1zGdYz9jA\nXXdjizn8AJhjHLOAallfPtvCfTEgKanhdoyz5FnhQE8HbDAop/KNS1lN2UMvdl5f\nZNLTSjJrNtedqxQwxN/i3bpyBxNVejUH2NjV1mmyj+5CJYwCzWalvI/gLPq/A3as\nO2EQqtf3U8unRgn0zXLRdYxV9MrUzNAmdipPNvNrsVdrCgA42rgF/8KsyRVQfJCX\nfN7PGCfrsC3YaUvhymraWxNnXIzMYTNa9wEeBZLUw8SlEtpa1Zsvui+TPXu3USNZ\nVnWH8Lb6ENlnoX0VBwo62fjOG3JzhNKoJawi3bRqyDdINOvafr7iPrrs/T8CAwEA\nAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUNpx1Pc6cGA7KqEwHMmHB\nTZMA7lQwDQYJKoZIhvcNAQEFBQADggEBAMoRgH3iTG3t317viLKoY+lNMHUgHuR7\nb3mn9MidJKyYVewe6hCDIN6WY4fUojmMW9wFJWJIo0hRMNHL3n3tq8HP2j20Mxy8\nacPdfGZJa+jiBw72CrIGdobKaFduIlIEDBA1pNdZIJ+EulrtqrMesnIt92WaypIS\n8JycbIgDMCiyC0ENHEk8UWlC6429c7OZAsplMTbHME/1R4btxjkdfrYZJjdJ2yL2\n8cjZDUDMCPTdW/ycP07Gkq30RB5tACB5aZdaCn2YaKC8FsEdhff4X7xEOfOEHWEq\nSRxADDj8Lx1MT6QpR07hCiDyHfTCtbqzI0iGjX63Oh7xXSa0f+JVTa8=\n-----END CERTIFICATE-----\n",
"securePort": 443
}
}
`)
})
}
func mockUpdateResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"sslTermination": {
"enabled": true,
"securePort": 443,
"secureTrafficOnly": false,
"privateKey": "foo",
"certificate": "-----BEGIN CERTIFICATE-----\nMIIEXTCCA0WgAwIBAgIGATTEAjK3MA0GCSqGSIb3DQEBBQUAMIGDMRkwFwYDVQQD\nExBUZXN0IENBIFNUdWIgS2V5MRcwFQYDVQQLEw5QbGF0Zm9ybSBMYmFhczEaMBgG\nA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFDASBgNVBAcTC1NhbiBBbnRvbmlvMQ4w\nDAYDVQQIEwVUZXhhczELMAkGA1UEBhMCVVMwHhcNMTIwMTA5MTk0NjQ1WhcNMTQw\nMTA4MTk0NjQ1WjCBgjELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMRQwEgYD\nVQQHEwtTYW4gQW50b25pbzEaMBgGA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFzAV\nBgNVBAsTDlBsYXRmb3JtIExiYWFzMRgwFgYDVQQDEw9UZXN0IENsaWVudCBLZXkw\nggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAi51IylFnHtNLT8C0NVfc\nOBfAsP2D5es1qhrOWHCGlgAuDMksBsCc7FPo5PSBOmQ+6z8HtCFbrLoC5/Zx0F5b\nfVegjA+xKjI2HGASsYHHM0BFEH2UjUcJrWiMWtxQuW6Phbqulo7JwjmygMEmIkeK\nf+FtkE9mrq+E8K40/thrjxl3/ZcJD1+3dcp+ZuzVJ2t1E4iGKCx79IZFsysKiuf+\n+E0i6iGvvI6UcbcZxVxQj2TplJkFuoX5kDgClIX9Dr9y6aJ4SCh+GRhvHl+DTaz0\nnCvghachHZtIeztRDqxWApjOOzs93dSelrviMXDr8fqyEAGg7YIhgui0aZBsWCen\nAgMBAAGjgdUwgdIwgbAGA1UdIwSBqDCBpYAUNpx1Pc6cGA7KqEwHMmHBTZMA7lSh\ngYmkgYYwgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVU4IBATAd\nBgNVHQ4EFgQULueOfsjZZOHwJHZwBy6u0swnpccwDQYJKoZIhvcNAQEFBQADggEB\nAFNuqSVUaotUJoWDv4z7Kbi6JFpTjDht5ORw4BdVYlRD4h9DACAFzPrPV2ym/Osp\nhNMdZq6msZku7MdOSQVhdeGWrSNk3M8O9Hg7cVzPNXOF3iNoo3irQ5tURut44xs4\nWw5YWQqS9WyUY5snD8tm7Y1rQTPfhg+678xIq/zWCv/u+FSnfVv1nlhLVQkEeG/Y\ngh1uMaTIpUKTGEjIAGtpGP7wwIcXptR/HyfzhTUSTaWc1Ef7zoKT9LL5z3IV1hC2\njVWz+RwYs98LjMuksJFoHqRfWyYhCIym0jb6GTwaEmpxAjc+d7OLNQdnoEGoUYGP\nYjtfkRYg265ESMA+Kww4Xy8=\n-----END CERTIFICATE-----\n",
"intermediateCertificate": "-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzEZMBcGA1UEAxMQVGVz\ndCBDQSBTVHViIEtleTEXMBUGA1UECxMOUGxhdGZvcm0gTGJhYXMxGjAYBgNVBAoT\nEVJhY2tzcGFjZSBIb3N0aW5nMRQwEgYDVQQHEwtTYW4gQW50b25pbzEOMAwGA1UE\nCBMFVGV4YXMxCzAJBgNVBAYTAlVTMB4XDTEyMDEwOTE5NDU0OVoXDTE0MDEwODE5\nNDU0OVowgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVUzCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNh55lwTVwQvNoEZjq1zGdYz9jA\nXXdjizn8AJhjHLOAallfPtvCfTEgKanhdoyz5FnhQE8HbDAop/KNS1lN2UMvdl5f\nZNLTSjJrNtedqxQwxN/i3bpyBxNVejUH2NjV1mmyj+5CJYwCzWalvI/gLPq/A3as\nO2EQqtf3U8unRgn0zXLRdYxV9MrUzNAmdipPNvNrsVdrCgA42rgF/8KsyRVQfJCX\nfN7PGCfrsC3YaUvhymraWxNnXIzMYTNa9wEeBZLUw8SlEtpa1Zsvui+TPXu3USNZ\nVnWH8Lb6ENlnoX0VBwo62fjOG3JzhNKoJawi3bRqyDdINOvafr7iPrrs/T8CAwEA\nAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUNpx1Pc6cGA7KqEwHMmHB\nTZMA7lQwDQYJKoZIhvcNAQEFBQADggEBAMoRgH3iTG3t317viLKoY+lNMHUgHuR7\nb3mn9MidJKyYVewe6hCDIN6WY4fUojmMW9wFJWJIo0hRMNHL3n3tq8HP2j20Mxy8\nacPdfGZJa+jiBw72CrIGdobKaFduIlIEDBA1pNdZIJ+EulrtqrMesnIt92WaypIS\n8JycbIgDMCiyC0ENHEk8UWlC6429c7OZAsplMTbHME/1R4btxjkdfrYZJjdJ2yL2\n8cjZDUDMCPTdW/ycP07Gkq30RB5tACB5aZdaCn2YaKC8FsEdhff4X7xEOfOEHWEq\nSRxADDj8Lx1MT6QpR07hCiDyHfTCtbqzI0iGjX63Oh7xXSa0f+JVTa8=\n-----END CERTIFICATE-----\n"
}
}
`)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{}`)
})
}
func mockDeleteResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusOK)
})
}
func mockListCertsResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_certURL(lbID, 0), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"certificateMappings": [
{
"certificateMapping": {
"id": 123,
"hostName": "rackspace.com"
}
},
{
"certificateMapping": {
"id": 124,
"hostName": "*.rackspace.com"
}
}
]
}
`)
})
}
func mockAddCertResponse(t *testing.T, lbID int) {
th.Mux.HandleFunc(_certURL(lbID, 0), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"certificateMapping": {
"hostName": "rackspace.com",
"privateKey":"-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAwIudSMpRZx7TS0/AtDVX3DgXwLD9g+XrNaoazlhwhpYALgzJ\nLAbAnOxT6OT0gTpkPus/B7QhW6y6Auf2cdBeW31XoIwPsSoyNhxgErGBxzNARRB9\nlI1HCa1ojFrcULluj4W6rpaOycI5soDBJiJHin/hbZBPZq6vhPCuNP7Ya48Zd/2X\nCQ9ft3XKfmbs1SdrdROIhigse/SGRbMrCorn/vhNIuohr7yOlHG3GcVcUI9k6ZSZ\nBbqF+ZA4ApSF/Q6/cumieEgofhkYbx5fg02s9Jwr4IWnIR2bSHs7UQ6sVgKYzjs7\nPd3Unpa74jFw6/H6shABoO2CIYLotGmQbFgnpwIDAQABAoIBAQCBCQ+PCIclJHNV\ntUzfeCA5ZR4F9JbxHdRTUnxEbOB8UWotckQfTScoAvj4yvdQ42DrCZxj/UOdvFOs\nPufZvlp91bIz1alugWjE+p8n5+2hIaegoTyHoWZKBfxak0myj5KYfHZvKlbmv1ML\nXV4TwEVRfAIG+v87QTY/UUxuF5vR+BpKIbgUJLfPUFFvJUdl84qsJ44pToxaYUd/\nh5YAGC00U4ay1KVSAUnTkkPNZ0lPG/rWU6w6WcTvNRLMd8DzFLTKLOgQfHhbExAF\n+sXPWjWSzbBRP1O7fHqq96QQh4VFiY/7w9W+sDKQyV6Ul17OSXs6aZ4f+lq4rJTI\n1FG96YiBAoGBAO1tiH0h1oWDBYfJB3KJJ6CQQsDGwtHo/DEgznFVP4XwEVbZ98Ha\nBfBCn3sAybbaikyCV1Hwj7kfHMZPDHbrcUSFX7quu/2zPK+wO3lZKXSyu4YsguSa\nRedInN33PpdnlPhLyQdWSuD5sVHJDF6xn22vlyxeILH3ooLg2WOFMPmVAoGBAM+b\nUG/a7iyfpAQKYyuFAsXz6SeFaDY+ZYeX45L112H8Pu+Ie/qzon+bzLB9FIH8GP6+\nQpQgmm/p37U2gD1zChUv7iW6OfQBKk9rWvMpfRF6d7YHquElejhizfTZ+ntBV/VY\ndOYEczxhrdW7keLpatYaaWUy/VboRZmlz/9JGqVLAoGAHfqNmFc0cgk4IowEj7a3\ntTNh6ltub/i+FynwRykfazcDyXaeLPDtfQe8gVh5H8h6W+y9P9BjJVnDVVrX1RAn\nbiJ1EupLPF5sVDapW8ohTOXgfbGTGXBNUUW+4Nv+IDno+mz/RhjkPYHpnM0I7c/5\ntGzOZsC/2hjNgT8I0+MWav0CgYEAuULdJeQVlKalI6HtW2Gn1uRRVJ49H+LQkY6e\nW3+cw2jo9LI0CMWSphNvNrN3wIMp/vHj0fHCP0pSApDvIWbuQXfzKaGko7UCf7rK\nf6GvZRCHkV4IREBAb97j8bMvThxClMNqFfU0rFZyXP+0MOyhFQyertswrgQ6T+Fi\n2mnvKD8CgYAmJHP3NTDRMoMRyAzonJ6nEaGUbAgNmivTaUWMe0+leCvAdwD89gzC\nTKbm3eDUg/6Va3X6ANh3wsfIOe4RXXxcbcFDk9R4zO2M5gfLSjYm5Q87EBZ2hrdj\nM2gLI7dt6thx0J8lR8xRHBEMrVBdgwp0g1gQzo5dAV88/kpkZVps8Q==\n-----END RSA PRIVATE KEY-----\n",
"certificate":"-----BEGIN CERTIFICATE-----\nMIIEXTCCA0WgAwIBAgIGATTEAjK3MA0GCSqGSIb3DQEBBQUAMIGDMRkwFwYDVQQD\nExBUZXN0IENBIFNUdWIgS2V5MRcwFQYDVQQLEw5QbGF0Zm9ybSBMYmFhczEaMBgG\nA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFDASBgNVBAcTC1NhbiBBbnRvbmlvMQ4w\nDAYDVQQIEwVUZXhhczELMAkGA1UEBhMCVVMwHhcNMTIwMTA5MTk0NjQ1WhcNMTQw\nMTA4MTk0NjQ1WjCBgjELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMRQwEgYD\nVQQHEwtTYW4gQW50b25pbzEaMBgGA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFzAV\nBgNVBAsTDlBsYXRmb3JtIExiYWFzMRgwFgYDVQQDEw9UZXN0IENsaWVudCBLZXkw\nggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAi51IylFnHtNLT8C0NVfc\nOBfAsP2D5es1qhrOWHCGlgAuDMksBsCc7FPo5PSBOmQ+6z8HtCFbrLoC5/Zx0F5b\nfVegjA+xKjI2HGASsYHHM0BFEH2UjUcJrWiMWtxQuW6Phbqulo7JwjmygMEmIkeK\nf+FtkE9mrq+E8K40/thrjxl3/ZcJD1+3dcp+ZuzVJ2t1E4iGKCx79IZFsysKiuf+\n+E0i6iGvvI6UcbcZxVxQj2TplJkFuoX5kDgClIX9Dr9y6aJ4SCh+GRhvHl+DTaz0\nnCvghachHZtIeztRDqxWApjOOzs93dSelrviMXDr8fqyEAGg7YIhgui0aZBsWCen\nAgMBAAGjgdUwgdIwgbAGA1UdIwSBqDCBpYAUNpx1Pc6cGA7KqEwHMmHBTZMA7lSh\ngYmkgYYwgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVU4IBATAd\nBgNVHQ4EFgQULueOfsjZZOHwJHZwBy6u0swnpccwDQYJKoZIhvcNAQEFBQADggEB\nAFNuqSVUaotUJoWDv4z7Kbi6JFpTjDht5ORw4BdVYlRD4h9DACAFzPrPV2ym/Osp\nhNMdZq6msZku7MdOSQVhdeGWrSNk3M8O9Hg7cVzPNXOF3iNoo3irQ5tURut44xs4\nWw5YWQqS9WyUY5snD8tm7Y1rQTPfhg+678xIq/zWCv/u+FSnfVv1nlhLVQkEeG/Y\ngh1uMaTIpUKTGEjIAGtpGP7wwIcXptR/HyfzhTUSTaWc1Ef7zoKT9LL5z3IV1hC2\njVWz+RwYs98LjMuksJFoHqRfWyYhCIym0jb6GTwaEmpxAjc+d7OLNQdnoEGoUYGP\nYjtfkRYg265ESMA+Kww4Xy8=\n-----END CERTIFICATE-----\n",
"intermediateCertificate":"-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzEZMBcGA1UEAxMQVGVz\ndCBDQSBTVHViIEtleTEXMBUGA1UECxMOUGxhdGZvcm0gTGJhYXMxGjAYBgNVBAoT\nEVJhY2tzcGFjZSBIb3N0aW5nMRQwEgYDVQQHEwtTYW4gQW50b25pbzEOMAwGA1UE\nCBMFVGV4YXMxCzAJBgNVBAYTAlVTMB4XDTEyMDEwOTE5NDU0OVoXDTE0MDEwODE5\nNDU0OVowgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVUzCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNh55lwTVwQvNoEZjq1zGdYz9jA\nXXdjizn8AJhjHLOAallfPtvCfTEgKanhdoyz5FnhQE8HbDAop/KNS1lN2UMvdl5f\nZNLTSjJrNtedqxQwxN/i3bpyBxNVejUH2NjV1mmyj+5CJYwCzWalvI/gLPq/A3as\nO2EQqtf3U8unRgn0zXLRdYxV9MrUzNAmdipPNvNrsVdrCgA42rgF/8KsyRVQfJCX\nfN7PGCfrsC3YaUvhymraWxNnXIzMYTNa9wEeBZLUw8SlEtpa1Zsvui+TPXu3USNZ\nVnWH8Lb6ENlnoX0VBwo62fjOG3JzhNKoJawi3bRqyDdINOvafr7iPrrs/T8CAwEA\nAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUNpx1Pc6cGA7KqEwHMmHB\nTZMA7lQwDQYJKoZIhvcNAQEFBQADggEBAMoRgH3iTG3t317viLKoY+lNMHUgHuR7\nb3mn9MidJKyYVewe6hCDIN6WY4fUojmMW9wFJWJIo0hRMNHL3n3tq8HP2j20Mxy8\nacPdfGZJa+jiBw72CrIGdobKaFduIlIEDBA1pNdZIJ+EulrtqrMesnIt92WaypIS\n8JycbIgDMCiyC0ENHEk8UWlC6429c7OZAsplMTbHME/1R4btxjkdfrYZJjdJ2yL2\n8cjZDUDMCPTdW/ycP07Gkq30RB5tACB5aZdaCn2YaKC8FsEdhff4X7xEOfOEHWEq\nSRxADDj8Lx1MT6QpR07hCiDyHfTCtbqzI0iGjX63Oh7xXSa0f+JVTa8=\n-----END CERTIFICATE-----\n"
}
}
`)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"certificateMapping": {
"id": 123,
"hostName": "rackspace.com",
"certificate":"-----BEGIN CERTIFICATE-----\nMIIEXTCCA0WgAwIBAgIGATTEAjK3MA0GCSqGSIb3DQEBBQUAMIGDMRkwFwYDVQQD\nExBUZXN0IENBIFNUdWIgS2V5MRcwFQYDVQQLEw5QbGF0Zm9ybSBMYmFhczEaMBgG\nA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFDASBgNVBAcTC1NhbiBBbnRvbmlvMQ4w\nDAYDVQQIEwVUZXhhczELMAkGA1UEBhMCVVMwHhcNMTIwMTA5MTk0NjQ1WhcNMTQw\nMTA4MTk0NjQ1WjCBgjELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMRQwEgYD\nVQQHEwtTYW4gQW50b25pbzEaMBgGA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFzAV\nBgNVBAsTDlBsYXRmb3JtIExiYWFzMRgwFgYDVQQDEw9UZXN0IENsaWVudCBLZXkw\nggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAi51IylFnHtNLT8C0NVfc\nOBfAsP2D5es1qhrOWHCGlgAuDMksBsCc7FPo5PSBOmQ+6z8HtCFbrLoC5/Zx0F5b\nfVegjA+xKjI2HGASsYHHM0BFEH2UjUcJrWiMWtxQuW6Phbqulo7JwjmygMEmIkeK\nf+FtkE9mrq+E8K40/thrjxl3/ZcJD1+3dcp+ZuzVJ2t1E4iGKCx79IZFsysKiuf+\n+E0i6iGvvI6UcbcZxVxQj2TplJkFuoX5kDgClIX9Dr9y6aJ4SCh+GRhvHl+DTaz0\nnCvghachHZtIeztRDqxWApjOOzs93dSelrviMXDr8fqyEAGg7YIhgui0aZBsWCen\nAgMBAAGjgdUwgdIwgbAGA1UdIwSBqDCBpYAUNpx1Pc6cGA7KqEwHMmHBTZMA7lSh\ngYmkgYYwgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVU4IBATAd\nBgNVHQ4EFgQULueOfsjZZOHwJHZwBy6u0swnpccwDQYJKoZIhvcNAQEFBQADggEB\nAFNuqSVUaotUJoWDv4z7Kbi6JFpTjDht5ORw4BdVYlRD4h9DACAFzPrPV2ym/Osp\nhNMdZq6msZku7MdOSQVhdeGWrSNk3M8O9Hg7cVzPNXOF3iNoo3irQ5tURut44xs4\nWw5YWQqS9WyUY5snD8tm7Y1rQTPfhg+678xIq/zWCv/u+FSnfVv1nlhLVQkEeG/Y\ngh1uMaTIpUKTGEjIAGtpGP7wwIcXptR/HyfzhTUSTaWc1Ef7zoKT9LL5z3IV1hC2\njVWz+RwYs98LjMuksJFoHqRfWyYhCIym0jb6GTwaEmpxAjc+d7OLNQdnoEGoUYGP\nYjtfkRYg265ESMA+Kww4Xy8=\n-----END CERTIFICATE-----\n",
"intermediateCertificate":"-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzEZMBcGA1UEAxMQVGVz\ndCBDQSBTVHViIEtleTEXMBUGA1UECxMOUGxhdGZvcm0gTGJhYXMxGjAYBgNVBAoT\nEVJhY2tzcGFjZSBIb3N0aW5nMRQwEgYDVQQHEwtTYW4gQW50b25pbzEOMAwGA1UE\nCBMFVGV4YXMxCzAJBgNVBAYTAlVTMB4XDTEyMDEwOTE5NDU0OVoXDTE0MDEwODE5\nNDU0OVowgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVUzCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNh55lwTVwQvNoEZjq1zGdYz9jA\nXXdjizn8AJhjHLOAallfPtvCfTEgKanhdoyz5FnhQE8HbDAop/KNS1lN2UMvdl5f\nZNLTSjJrNtedqxQwxN/i3bpyBxNVejUH2NjV1mmyj+5CJYwCzWalvI/gLPq/A3as\nO2EQqtf3U8unRgn0zXLRdYxV9MrUzNAmdipPNvNrsVdrCgA42rgF/8KsyRVQfJCX\nfN7PGCfrsC3YaUvhymraWxNnXIzMYTNa9wEeBZLUw8SlEtpa1Zsvui+TPXu3USNZ\nVnWH8Lb6ENlnoX0VBwo62fjOG3JzhNKoJawi3bRqyDdINOvafr7iPrrs/T8CAwEA\nAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUNpx1Pc6cGA7KqEwHMmHB\nTZMA7lQwDQYJKoZIhvcNAQEFBQADggEBAMoRgH3iTG3t317viLKoY+lNMHUgHuR7\nb3mn9MidJKyYVewe6hCDIN6WY4fUojmMW9wFJWJIo0hRMNHL3n3tq8HP2j20Mxy8\nacPdfGZJa+jiBw72CrIGdobKaFduIlIEDBA1pNdZIJ+EulrtqrMesnIt92WaypIS\n8JycbIgDMCiyC0ENHEk8UWlC6429c7OZAsplMTbHME/1R4btxjkdfrYZJjdJ2yL2\n8cjZDUDMCPTdW/ycP07Gkq30RB5tACB5aZdaCn2YaKC8FsEdhff4X7xEOfOEHWEq\nSRxADDj8Lx1MT6QpR07hCiDyHfTCtbqzI0iGjX63Oh7xXSa0f+JVTa8=\n-----END CERTIFICATE-----\n"
}
}
`)
})
}
func mockGetCertResponse(t *testing.T, lbID, certID int) {
th.Mux.HandleFunc(_certURL(lbID, certID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"certificateMapping": {
"id": 123,
"hostName": "rackspace.com",
"certificate":"-----BEGIN CERTIFICATE-----\nMIIEXTCCA0WgAwIBAgIGATTEAjK3MA0GCSqGSIb3DQEBBQUAMIGDMRkwFwYDVQQD\nExBUZXN0IENBIFNUdWIgS2V5MRcwFQYDVQQLEw5QbGF0Zm9ybSBMYmFhczEaMBgG\nA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFDASBgNVBAcTC1NhbiBBbnRvbmlvMQ4w\nDAYDVQQIEwVUZXhhczELMAkGA1UEBhMCVVMwHhcNMTIwMTA5MTk0NjQ1WhcNMTQw\nMTA4MTk0NjQ1WjCBgjELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMRQwEgYD\nVQQHEwtTYW4gQW50b25pbzEaMBgGA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFzAV\nBgNVBAsTDlBsYXRmb3JtIExiYWFzMRgwFgYDVQQDEw9UZXN0IENsaWVudCBLZXkw\nggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAi51IylFnHtNLT8C0NVfc\nOBfAsP2D5es1qhrOWHCGlgAuDMksBsCc7FPo5PSBOmQ+6z8HtCFbrLoC5/Zx0F5b\nfVegjA+xKjI2HGASsYHHM0BFEH2UjUcJrWiMWtxQuW6Phbqulo7JwjmygMEmIkeK\nf+FtkE9mrq+E8K40/thrjxl3/ZcJD1+3dcp+ZuzVJ2t1E4iGKCx79IZFsysKiuf+\n+E0i6iGvvI6UcbcZxVxQj2TplJkFuoX5kDgClIX9Dr9y6aJ4SCh+GRhvHl+DTaz0\nnCvghachHZtIeztRDqxWApjOOzs93dSelrviMXDr8fqyEAGg7YIhgui0aZBsWCen\nAgMBAAGjgdUwgdIwgbAGA1UdIwSBqDCBpYAUNpx1Pc6cGA7KqEwHMmHBTZMA7lSh\ngYmkgYYwgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVU4IBATAd\nBgNVHQ4EFgQULueOfsjZZOHwJHZwBy6u0swnpccwDQYJKoZIhvcNAQEFBQADggEB\nAFNuqSVUaotUJoWDv4z7Kbi6JFpTjDht5ORw4BdVYlRD4h9DACAFzPrPV2ym/Osp\nhNMdZq6msZku7MdOSQVhdeGWrSNk3M8O9Hg7cVzPNXOF3iNoo3irQ5tURut44xs4\nWw5YWQqS9WyUY5snD8tm7Y1rQTPfhg+678xIq/zWCv/u+FSnfVv1nlhLVQkEeG/Y\ngh1uMaTIpUKTGEjIAGtpGP7wwIcXptR/HyfzhTUSTaWc1Ef7zoKT9LL5z3IV1hC2\njVWz+RwYs98LjMuksJFoHqRfWyYhCIym0jb6GTwaEmpxAjc+d7OLNQdnoEGoUYGP\nYjtfkRYg265ESMA+Kww4Xy8=\n-----END CERTIFICATE-----\n",
"intermediateCertificate":"-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzEZMBcGA1UEAxMQVGVz\ndCBDQSBTVHViIEtleTEXMBUGA1UECxMOUGxhdGZvcm0gTGJhYXMxGjAYBgNVBAoT\nEVJhY2tzcGFjZSBIb3N0aW5nMRQwEgYDVQQHEwtTYW4gQW50b25pbzEOMAwGA1UE\nCBMFVGV4YXMxCzAJBgNVBAYTAlVTMB4XDTEyMDEwOTE5NDU0OVoXDTE0MDEwODE5\nNDU0OVowgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVUzCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNh55lwTVwQvNoEZjq1zGdYz9jA\nXXdjizn8AJhjHLOAallfPtvCfTEgKanhdoyz5FnhQE8HbDAop/KNS1lN2UMvdl5f\nZNLTSjJrNtedqxQwxN/i3bpyBxNVejUH2NjV1mmyj+5CJYwCzWalvI/gLPq/A3as\nO2EQqtf3U8unRgn0zXLRdYxV9MrUzNAmdipPNvNrsVdrCgA42rgF/8KsyRVQfJCX\nfN7PGCfrsC3YaUvhymraWxNnXIzMYTNa9wEeBZLUw8SlEtpa1Zsvui+TPXu3USNZ\nVnWH8Lb6ENlnoX0VBwo62fjOG3JzhNKoJawi3bRqyDdINOvafr7iPrrs/T8CAwEA\nAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUNpx1Pc6cGA7KqEwHMmHB\nTZMA7lQwDQYJKoZIhvcNAQEFBQADggEBAMoRgH3iTG3t317viLKoY+lNMHUgHuR7\nb3mn9MidJKyYVewe6hCDIN6WY4fUojmMW9wFJWJIo0hRMNHL3n3tq8HP2j20Mxy8\nacPdfGZJa+jiBw72CrIGdobKaFduIlIEDBA1pNdZIJ+EulrtqrMesnIt92WaypIS\n8JycbIgDMCiyC0ENHEk8UWlC6429c7OZAsplMTbHME/1R4btxjkdfrYZJjdJ2yL2\n8cjZDUDMCPTdW/ycP07Gkq30RB5tACB5aZdaCn2YaKC8FsEdhff4X7xEOfOEHWEq\nSRxADDj8Lx1MT6QpR07hCiDyHfTCtbqzI0iGjX63Oh7xXSa0f+JVTa8=\n-----END CERTIFICATE-----\n"
}
}
`)
})
}
func mockUpdateCertResponse(t *testing.T, lbID, certID int) {
th.Mux.HandleFunc(_certURL(lbID, certID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestJSONRequest(t, r, `
{
"certificateMapping": {
"hostName": "rackspace.com",
"privateKey":"-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAwIudSMpRZx7TS0/AtDVX3DgXwLD9g+XrNaoazlhwhpYALgzJ\nLAbAnOxT6OT0gTpkPus/B7QhW6y6Auf2cdBeW31XoIwPsSoyNhxgErGBxzNARRB9\nlI1HCa1ojFrcULluj4W6rpaOycI5soDBJiJHin/hbZBPZq6vhPCuNP7Ya48Zd/2X\nCQ9ft3XKfmbs1SdrdROIhigse/SGRbMrCorn/vhNIuohr7yOlHG3GcVcUI9k6ZSZ\nBbqF+ZA4ApSF/Q6/cumieEgofhkYbx5fg02s9Jwr4IWnIR2bSHs7UQ6sVgKYzjs7\nPd3Unpa74jFw6/H6shABoO2CIYLotGmQbFgnpwIDAQABAoIBAQCBCQ+PCIclJHNV\ntUzfeCA5ZR4F9JbxHdRTUnxEbOB8UWotckQfTScoAvj4yvdQ42DrCZxj/UOdvFOs\nPufZvlp91bIz1alugWjE+p8n5+2hIaegoTyHoWZKBfxak0myj5KYfHZvKlbmv1ML\nXV4TwEVRfAIG+v87QTY/UUxuF5vR+BpKIbgUJLfPUFFvJUdl84qsJ44pToxaYUd/\nh5YAGC00U4ay1KVSAUnTkkPNZ0lPG/rWU6w6WcTvNRLMd8DzFLTKLOgQfHhbExAF\n+sXPWjWSzbBRP1O7fHqq96QQh4VFiY/7w9W+sDKQyV6Ul17OSXs6aZ4f+lq4rJTI\n1FG96YiBAoGBAO1tiH0h1oWDBYfJB3KJJ6CQQsDGwtHo/DEgznFVP4XwEVbZ98Ha\nBfBCn3sAybbaikyCV1Hwj7kfHMZPDHbrcUSFX7quu/2zPK+wO3lZKXSyu4YsguSa\nRedInN33PpdnlPhLyQdWSuD5sVHJDF6xn22vlyxeILH3ooLg2WOFMPmVAoGBAM+b\nUG/a7iyfpAQKYyuFAsXz6SeFaDY+ZYeX45L112H8Pu+Ie/qzon+bzLB9FIH8GP6+\nQpQgmm/p37U2gD1zChUv7iW6OfQBKk9rWvMpfRF6d7YHquElejhizfTZ+ntBV/VY\ndOYEczxhrdW7keLpatYaaWUy/VboRZmlz/9JGqVLAoGAHfqNmFc0cgk4IowEj7a3\ntTNh6ltub/i+FynwRykfazcDyXaeLPDtfQe8gVh5H8h6W+y9P9BjJVnDVVrX1RAn\nbiJ1EupLPF5sVDapW8ohTOXgfbGTGXBNUUW+4Nv+IDno+mz/RhjkPYHpnM0I7c/5\ntGzOZsC/2hjNgT8I0+MWav0CgYEAuULdJeQVlKalI6HtW2Gn1uRRVJ49H+LQkY6e\nW3+cw2jo9LI0CMWSphNvNrN3wIMp/vHj0fHCP0pSApDvIWbuQXfzKaGko7UCf7rK\nf6GvZRCHkV4IREBAb97j8bMvThxClMNqFfU0rFZyXP+0MOyhFQyertswrgQ6T+Fi\n2mnvKD8CgYAmJHP3NTDRMoMRyAzonJ6nEaGUbAgNmivTaUWMe0+leCvAdwD89gzC\nTKbm3eDUg/6Va3X6ANh3wsfIOe4RXXxcbcFDk9R4zO2M5gfLSjYm5Q87EBZ2hrdj\nM2gLI7dt6thx0J8lR8xRHBEMrVBdgwp0g1gQzo5dAV88/kpkZVps8Q==\n-----END RSA PRIVATE KEY-----\n",
"certificate":"-----BEGIN CERTIFICATE-----\nMIIEXTCCA0WgAwIBAgIGATTEAjK3MA0GCSqGSIb3DQEBBQUAMIGDMRkwFwYDVQQD\nExBUZXN0IENBIFNUdWIgS2V5MRcwFQYDVQQLEw5QbGF0Zm9ybSBMYmFhczEaMBgG\nA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFDASBgNVBAcTC1NhbiBBbnRvbmlvMQ4w\nDAYDVQQIEwVUZXhhczELMAkGA1UEBhMCVVMwHhcNMTIwMTA5MTk0NjQ1WhcNMTQw\nMTA4MTk0NjQ1WjCBgjELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMRQwEgYD\nVQQHEwtTYW4gQW50b25pbzEaMBgGA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFzAV\nBgNVBAsTDlBsYXRmb3JtIExiYWFzMRgwFgYDVQQDEw9UZXN0IENsaWVudCBLZXkw\nggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAi51IylFnHtNLT8C0NVfc\nOBfAsP2D5es1qhrOWHCGlgAuDMksBsCc7FPo5PSBOmQ+6z8HtCFbrLoC5/Zx0F5b\nfVegjA+xKjI2HGASsYHHM0BFEH2UjUcJrWiMWtxQuW6Phbqulo7JwjmygMEmIkeK\nf+FtkE9mrq+E8K40/thrjxl3/ZcJD1+3dcp+ZuzVJ2t1E4iGKCx79IZFsysKiuf+\n+E0i6iGvvI6UcbcZxVxQj2TplJkFuoX5kDgClIX9Dr9y6aJ4SCh+GRhvHl+DTaz0\nnCvghachHZtIeztRDqxWApjOOzs93dSelrviMXDr8fqyEAGg7YIhgui0aZBsWCen\nAgMBAAGjgdUwgdIwgbAGA1UdIwSBqDCBpYAUNpx1Pc6cGA7KqEwHMmHBTZMA7lSh\ngYmkgYYwgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVU4IBATAd\nBgNVHQ4EFgQULueOfsjZZOHwJHZwBy6u0swnpccwDQYJKoZIhvcNAQEFBQADggEB\nAFNuqSVUaotUJoWDv4z7Kbi6JFpTjDht5ORw4BdVYlRD4h9DACAFzPrPV2ym/Osp\nhNMdZq6msZku7MdOSQVhdeGWrSNk3M8O9Hg7cVzPNXOF3iNoo3irQ5tURut44xs4\nWw5YWQqS9WyUY5snD8tm7Y1rQTPfhg+678xIq/zWCv/u+FSnfVv1nlhLVQkEeG/Y\ngh1uMaTIpUKTGEjIAGtpGP7wwIcXptR/HyfzhTUSTaWc1Ef7zoKT9LL5z3IV1hC2\njVWz+RwYs98LjMuksJFoHqRfWyYhCIym0jb6GTwaEmpxAjc+d7OLNQdnoEGoUYGP\nYjtfkRYg265ESMA+Kww4Xy8=\n-----END CERTIFICATE-----\n",
"intermediateCertificate":"-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzEZMBcGA1UEAxMQVGVz\ndCBDQSBTVHViIEtleTEXMBUGA1UECxMOUGxhdGZvcm0gTGJhYXMxGjAYBgNVBAoT\nEVJhY2tzcGFjZSBIb3N0aW5nMRQwEgYDVQQHEwtTYW4gQW50b25pbzEOMAwGA1UE\nCBMFVGV4YXMxCzAJBgNVBAYTAlVTMB4XDTEyMDEwOTE5NDU0OVoXDTE0MDEwODE5\nNDU0OVowgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVUzCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNh55lwTVwQvNoEZjq1zGdYz9jA\nXXdjizn8AJhjHLOAallfPtvCfTEgKanhdoyz5FnhQE8HbDAop/KNS1lN2UMvdl5f\nZNLTSjJrNtedqxQwxN/i3bpyBxNVejUH2NjV1mmyj+5CJYwCzWalvI/gLPq/A3as\nO2EQqtf3U8unRgn0zXLRdYxV9MrUzNAmdipPNvNrsVdrCgA42rgF/8KsyRVQfJCX\nfN7PGCfrsC3YaUvhymraWxNnXIzMYTNa9wEeBZLUw8SlEtpa1Zsvui+TPXu3USNZ\nVnWH8Lb6ENlnoX0VBwo62fjOG3JzhNKoJawi3bRqyDdINOvafr7iPrrs/T8CAwEA\nAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUNpx1Pc6cGA7KqEwHMmHB\nTZMA7lQwDQYJKoZIhvcNAQEFBQADggEBAMoRgH3iTG3t317viLKoY+lNMHUgHuR7\nb3mn9MidJKyYVewe6hCDIN6WY4fUojmMW9wFJWJIo0hRMNHL3n3tq8HP2j20Mxy8\nacPdfGZJa+jiBw72CrIGdobKaFduIlIEDBA1pNdZIJ+EulrtqrMesnIt92WaypIS\n8JycbIgDMCiyC0ENHEk8UWlC6429c7OZAsplMTbHME/1R4btxjkdfrYZJjdJ2yL2\n8cjZDUDMCPTdW/ycP07Gkq30RB5tACB5aZdaCn2YaKC8FsEdhff4X7xEOfOEHWEq\nSRxADDj8Lx1MT6QpR07hCiDyHfTCtbqzI0iGjX63Oh7xXSa0f+JVTa8=\n-----END CERTIFICATE-----\n"
}
}
`)
w.WriteHeader(http.StatusAccepted)
fmt.Fprintf(w, `
{
"certificateMapping": {
"id": 123,
"hostName": "rackspace.com",
"certificate":"-----BEGIN CERTIFICATE-----\nMIIEXTCCA0WgAwIBAgIGATTEAjK3MA0GCSqGSIb3DQEBBQUAMIGDMRkwFwYDVQQD\nExBUZXN0IENBIFNUdWIgS2V5MRcwFQYDVQQLEw5QbGF0Zm9ybSBMYmFhczEaMBgG\nA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFDASBgNVBAcTC1NhbiBBbnRvbmlvMQ4w\nDAYDVQQIEwVUZXhhczELMAkGA1UEBhMCVVMwHhcNMTIwMTA5MTk0NjQ1WhcNMTQw\nMTA4MTk0NjQ1WjCBgjELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMRQwEgYD\nVQQHEwtTYW4gQW50b25pbzEaMBgGA1UEChMRUmFja3NwYWNlIEhvc3RpbmcxFzAV\nBgNVBAsTDlBsYXRmb3JtIExiYWFzMRgwFgYDVQQDEw9UZXN0IENsaWVudCBLZXkw\nggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAi51IylFnHtNLT8C0NVfc\nOBfAsP2D5es1qhrOWHCGlgAuDMksBsCc7FPo5PSBOmQ+6z8HtCFbrLoC5/Zx0F5b\nfVegjA+xKjI2HGASsYHHM0BFEH2UjUcJrWiMWtxQuW6Phbqulo7JwjmygMEmIkeK\nf+FtkE9mrq+E8K40/thrjxl3/ZcJD1+3dcp+ZuzVJ2t1E4iGKCx79IZFsysKiuf+\n+E0i6iGvvI6UcbcZxVxQj2TplJkFuoX5kDgClIX9Dr9y6aJ4SCh+GRhvHl+DTaz0\nnCvghachHZtIeztRDqxWApjOOzs93dSelrviMXDr8fqyEAGg7YIhgui0aZBsWCen\nAgMBAAGjgdUwgdIwgbAGA1UdIwSBqDCBpYAUNpx1Pc6cGA7KqEwHMmHBTZMA7lSh\ngYmkgYYwgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVU4IBATAd\nBgNVHQ4EFgQULueOfsjZZOHwJHZwBy6u0swnpccwDQYJKoZIhvcNAQEFBQADggEB\nAFNuqSVUaotUJoWDv4z7Kbi6JFpTjDht5ORw4BdVYlRD4h9DACAFzPrPV2ym/Osp\nhNMdZq6msZku7MdOSQVhdeGWrSNk3M8O9Hg7cVzPNXOF3iNoo3irQ5tURut44xs4\nWw5YWQqS9WyUY5snD8tm7Y1rQTPfhg+678xIq/zWCv/u+FSnfVv1nlhLVQkEeG/Y\ngh1uMaTIpUKTGEjIAGtpGP7wwIcXptR/HyfzhTUSTaWc1Ef7zoKT9LL5z3IV1hC2\njVWz+RwYs98LjMuksJFoHqRfWyYhCIym0jb6GTwaEmpxAjc+d7OLNQdnoEGoUYGP\nYjtfkRYg265ESMA+Kww4Xy8=\n-----END CERTIFICATE-----\n",
"intermediateCertificate":"-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzEZMBcGA1UEAxMQVGVz\ndCBDQSBTVHViIEtleTEXMBUGA1UECxMOUGxhdGZvcm0gTGJhYXMxGjAYBgNVBAoT\nEVJhY2tzcGFjZSBIb3N0aW5nMRQwEgYDVQQHEwtTYW4gQW50b25pbzEOMAwGA1UE\nCBMFVGV4YXMxCzAJBgNVBAYTAlVTMB4XDTEyMDEwOTE5NDU0OVoXDTE0MDEwODE5\nNDU0OVowgYMxGTAXBgNVBAMTEFRlc3QgQ0EgU1R1YiBLZXkxFzAVBgNVBAsTDlBs\nYXRmb3JtIExiYWFzMRowGAYDVQQKExFSYWNrc3BhY2UgSG9zdGluZzEUMBIGA1UE\nBxMLU2FuIEFudG9uaW8xDjAMBgNVBAgTBVRleGFzMQswCQYDVQQGEwJVUzCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNh55lwTVwQvNoEZjq1zGdYz9jA\nXXdjizn8AJhjHLOAallfPtvCfTEgKanhdoyz5FnhQE8HbDAop/KNS1lN2UMvdl5f\nZNLTSjJrNtedqxQwxN/i3bpyBxNVejUH2NjV1mmyj+5CJYwCzWalvI/gLPq/A3as\nO2EQqtf3U8unRgn0zXLRdYxV9MrUzNAmdipPNvNrsVdrCgA42rgF/8KsyRVQfJCX\nfN7PGCfrsC3YaUvhymraWxNnXIzMYTNa9wEeBZLUw8SlEtpa1Zsvui+TPXu3USNZ\nVnWH8Lb6ENlnoX0VBwo62fjOG3JzhNKoJawi3bRqyDdINOvafr7iPrrs/T8CAwEA\nAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUNpx1Pc6cGA7KqEwHMmHB\nTZMA7lQwDQYJKoZIhvcNAQEFBQADggEBAMoRgH3iTG3t317viLKoY+lNMHUgHuR7\nb3mn9MidJKyYVewe6hCDIN6WY4fUojmMW9wFJWJIo0hRMNHL3n3tq8HP2j20Mxy8\nacPdfGZJa+jiBw72CrIGdobKaFduIlIEDBA1pNdZIJ+EulrtqrMesnIt92WaypIS\n8JycbIgDMCiyC0ENHEk8UWlC6429c7OZAsplMTbHME/1R4btxjkdfrYZJjdJ2yL2\n8cjZDUDMCPTdW/ycP07Gkq30RB5tACB5aZdaCn2YaKC8FsEdhff4X7xEOfOEHWEq\nSRxADDj8Lx1MT6QpR07hCiDyHfTCtbqzI0iGjX63Oh7xXSa0f+JVTa8=\n-----END CERTIFICATE-----\n"
}
}
`)
})
}
func mockDeleteCertResponse(t *testing.T, lbID, certID int) {
th.Mux.HandleFunc(_certURL(lbID, certID), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusOK)
})
}

View File

@@ -1,247 +0,0 @@
package ssl
import (
"errors"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
var (
errPrivateKey = errors.New("PrivateKey is a required field")
errCertificate = errors.New("Certificate is a required field")
errIntCertificate = errors.New("IntCertificate is a required field")
)
// UpdateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Update operation in this package.
type UpdateOptsBuilder interface {
ToSSLUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts is the common options struct used in this package's Update
// operation.
type UpdateOpts struct {
// Required - consult the SSLTermConfig struct for more info.
SecurePort int
// Required - consult the SSLTermConfig struct for more info.
PrivateKey string
// Required - consult the SSLTermConfig struct for more info.
Certificate string
// Required - consult the SSLTermConfig struct for more info.
IntCertificate string
// Optional - consult the SSLTermConfig struct for more info.
Enabled *bool
// Optional - consult the SSLTermConfig struct for more info.
SecureTrafficOnly *bool
}
// ToSSLUpdateMap casts a CreateOpts struct to a map.
func (opts UpdateOpts) ToSSLUpdateMap() (map[string]interface{}, error) {
ssl := make(map[string]interface{})
if opts.SecurePort == 0 {
return ssl, errors.New("SecurePort needs to be an integer greater than 0")
}
if opts.PrivateKey == "" {
return ssl, errPrivateKey
}
if opts.Certificate == "" {
return ssl, errCertificate
}
if opts.IntCertificate == "" {
return ssl, errIntCertificate
}
ssl["securePort"] = opts.SecurePort
ssl["privateKey"] = opts.PrivateKey
ssl["certificate"] = opts.Certificate
ssl["intermediateCertificate"] = opts.IntCertificate
if opts.Enabled != nil {
ssl["enabled"] = &opts.Enabled
}
if opts.SecureTrafficOnly != nil {
ssl["secureTrafficOnly"] = &opts.SecureTrafficOnly
}
return map[string]interface{}{"sslTermination": ssl}, nil
}
// Update is the operation responsible for updating the SSL Termination
// configuration for a load balancer.
func Update(c *gophercloud.ServiceClient, lbID int, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToSSLUpdateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = c.Put(rootURL(c, lbID), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}
// Get is the operation responsible for showing the details of the SSL
// Termination configuration for a load balancer.
func Get(c *gophercloud.ServiceClient, lbID int) GetResult {
var res GetResult
_, res.Err = c.Get(rootURL(c, lbID), &res.Body, nil)
return res
}
// Delete is the operation responsible for deleting the SSL Termination
// configuration for a load balancer.
func Delete(c *gophercloud.ServiceClient, lbID int) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(rootURL(c, lbID), &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}
// ListCerts will list all of the certificate mappings associated with a
// SSL-terminated HTTP load balancer.
func ListCerts(c *gophercloud.ServiceClient, lbID int) pagination.Pager {
url := certURL(c, lbID)
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return CertPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// CreateCertOptsBuilder is the interface options structs have to satisfy in
// order to be used in the AddCert operation in this package.
type CreateCertOptsBuilder interface {
ToCertCreateMap() (map[string]interface{}, error)
}
// CreateCertOpts represents the options used when adding a new certificate mapping.
type CreateCertOpts struct {
HostName string
PrivateKey string
Certificate string
IntCertificate string
}
// ToCertCreateMap will cast an CreateCertOpts struct to a map for JSON serialization.
func (opts CreateCertOpts) ToCertCreateMap() (map[string]interface{}, error) {
cm := make(map[string]interface{})
if opts.HostName == "" {
return cm, errors.New("HostName is a required option")
}
if opts.PrivateKey == "" {
return cm, errPrivateKey
}
if opts.Certificate == "" {
return cm, errCertificate
}
cm["hostName"] = opts.HostName
cm["privateKey"] = opts.PrivateKey
cm["certificate"] = opts.Certificate
if opts.IntCertificate != "" {
cm["intermediateCertificate"] = opts.IntCertificate
}
return map[string]interface{}{"certificateMapping": cm}, nil
}
// CreateCert will add a new SSL certificate and allow an SSL-terminated HTTP
// load balancer to use it. This feature is useful because it allows multiple
// certificates to be used. The maximum number of certificates that can be
// stored per LB is 20.
func CreateCert(c *gophercloud.ServiceClient, lbID int, opts CreateCertOptsBuilder) CreateCertResult {
var res CreateCertResult
reqBody, err := opts.ToCertCreateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = c.Post(certURL(c, lbID), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}
// GetCert will show the details of an existing SSL certificate.
func GetCert(c *gophercloud.ServiceClient, lbID, certID int) GetCertResult {
var res GetCertResult
_, res.Err = c.Get(certResourceURL(c, lbID, certID), &res.Body, nil)
return res
}
// UpdateCertOptsBuilder is the interface options structs have to satisfy in
// order to be used in the UpdateCert operation in this package.
type UpdateCertOptsBuilder interface {
ToCertUpdateMap() (map[string]interface{}, error)
}
// UpdateCertOpts represents the options needed to update a SSL certificate.
type UpdateCertOpts struct {
HostName string
PrivateKey string
Certificate string
IntCertificate string
}
// ToCertUpdateMap will cast an UpdateCertOpts struct into a map for JSON
// seralization.
func (opts UpdateCertOpts) ToCertUpdateMap() (map[string]interface{}, error) {
cm := make(map[string]interface{})
if opts.HostName != "" {
cm["hostName"] = opts.HostName
}
if opts.PrivateKey != "" {
cm["privateKey"] = opts.PrivateKey
}
if opts.Certificate != "" {
cm["certificate"] = opts.Certificate
}
if opts.IntCertificate != "" {
cm["intermediateCertificate"] = opts.IntCertificate
}
return map[string]interface{}{"certificateMapping": cm}, nil
}
// UpdateCert is the operation responsible for updating the details of an
// existing SSL certificate.
func UpdateCert(c *gophercloud.ServiceClient, lbID, certID int, opts UpdateCertOptsBuilder) UpdateCertResult {
var res UpdateCertResult
reqBody, err := opts.ToCertUpdateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = c.Put(certResourceURL(c, lbID, certID), reqBody, &res.Body, nil)
return res
}
// DeleteCert is the operation responsible for permanently removing a SSL
// certificate.
func DeleteCert(c *gophercloud.ServiceClient, lbID, certID int) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(certResourceURL(c, lbID, certID), &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}

View File

@@ -1,148 +0,0 @@
package ssl
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// SSLTermConfig represents the SSL configuration for a particular load balancer.
type SSLTermConfig struct {
// The port on which the SSL termination load balancer listens for secure
// traffic. The value must be unique to the existing LB protocol/port
// combination
SecurePort int `mapstructure:"securePort"`
// The private key for the SSL certificate which is validated and verified
// against the provided certificates.
PrivateKey string `mapstructure:"privatekey"`
// The certificate used for SSL termination, which is validated and verified
// against the key and intermediate certificate if provided.
Certificate string
// The intermediate certificate (for the user). The intermediate certificate
// is validated and verified against the key and certificate credentials
// provided. A user may only provide this value when accompanied by a
// Certificate, PrivateKey, and SecurePort. It may not be added or updated as
// a single attribute in a future operation.
IntCertificate string `mapstructure:"intermediatecertificate"`
// Determines if the load balancer is enabled to terminate SSL traffic or not.
// If this is set to false, the load balancer retains its specified SSL
// attributes but does not terminate SSL traffic.
Enabled bool
// Determines if the load balancer can only accept secure traffic. If set to
// true, the load balancer will not accept non-secure traffic.
SecureTrafficOnly bool
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}
// UpdateResult represents the result of an update operation.
type UpdateResult struct {
gophercloud.ErrResult
}
// GetResult represents the result of a get operation.
type GetResult struct {
gophercloud.Result
}
// Extract interprets a GetResult as a SSLTermConfig struct, if possible.
func (r GetResult) Extract() (*SSLTermConfig, error) {
if r.Err != nil {
return nil, r.Err
}
var response struct {
SSL SSLTermConfig `mapstructure:"sslTermination"`
}
err := mapstructure.Decode(r.Body, &response)
return &response.SSL, err
}
// Certificate represents an SSL certificate associated with an SSL-terminated
// HTTP load balancer.
type Certificate struct {
ID int
HostName string
Certificate string
IntCertificate string `mapstructure:"intermediateCertificate"`
}
// CertPage represents a page of certificates.
type CertPage struct {
pagination.LinkedPageBase
}
// IsEmpty checks whether a CertMappingPage struct is empty.
func (p CertPage) IsEmpty() (bool, error) {
is, err := ExtractCerts(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
}
// ExtractCerts accepts a Page struct, specifically a CertPage struct, and
// extracts the elements into a slice of Cert structs. In other words, a generic
// collection is mapped into a relevant slice.
func ExtractCerts(page pagination.Page) ([]Certificate, error) {
type NestedMap struct {
Cert Certificate `mapstructure:"certificateMapping" json:"certificateMapping"`
}
var resp struct {
Certs []NestedMap `mapstructure:"certificateMappings" json:"certificateMappings"`
}
err := mapstructure.Decode(page.(CertPage).Body, &resp)
slice := []Certificate{}
for _, cert := range resp.Certs {
slice = append(slice, cert.Cert)
}
return slice, err
}
type certResult struct {
gophercloud.Result
}
// Extract interprets a result as a CertMapping struct, if possible.
func (r certResult) Extract() (*Certificate, error) {
if r.Err != nil {
return nil, r.Err
}
var response struct {
Cert Certificate `mapstructure:"certificateMapping"`
}
err := mapstructure.Decode(r.Body, &response)
return &response.Cert, err
}
// CreateCertResult represents the result of an CreateCert operation.
type CreateCertResult struct {
certResult
}
// GetCertResult represents the result of a GetCert operation.
type GetCertResult struct {
certResult
}
// UpdateCertResult represents the result of an UpdateCert operation.
type UpdateCertResult struct {
certResult
}

View File

@@ -1,25 +0,0 @@
package ssl
import (
"strconv"
"github.com/rackspace/gophercloud"
)
const (
path = "loadbalancers"
sslPath = "ssltermination"
certPath = "certificatemappings"
)
func rootURL(c *gophercloud.ServiceClient, id int) string {
return c.ServiceURL(path, strconv.Itoa(id), sslPath)
}
func certURL(c *gophercloud.ServiceClient, id int) string {
return c.ServiceURL(path, strconv.Itoa(id), sslPath, certPath)
}
func certResourceURL(c *gophercloud.ServiceClient, id, certID int) string {
return c.ServiceURL(path, strconv.Itoa(id), sslPath, certPath, strconv.Itoa(certID))
}

View File

@@ -1,5 +0,0 @@
/*
Package throttle provides information and interaction with the Connection
Throttling feature of the Rackspace Cloud Load Balancer service.
*/
package throttle

Some files were not shown because too many files have changed in this diff Show More