Add dependencies for OpenStack driver

Signed-off-by: Guillaume Giamarchi <guillaume.giamarchi@gmail.com>
This commit is contained in:
Guillaume Giamarchi 2014-12-09 13:31:28 +01:00
parent c2f1fea9dc
commit 1785869490
536 changed files with 47539 additions and 0 deletions

14
Godeps/Godeps.json generated
View File

@ -118,6 +118,20 @@
"ImportPath": "github.com/smartystreets/go-aws-auth", "ImportPath": "github.com/smartystreets/go-aws-auth",
"Rev": "1f0db8c0ee6362470abe06a94e3385927ed72a4b" "Rev": "1f0db8c0ee6362470abe06a94e3385927ed72a4b"
}, },
{
"ImportPath": "github.com/mitchellh/mapstructure",
"Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf"
},
{
"ImportPath": "github.com/racker/perigee",
"Comment": "v0.0.0-18-g0c00cb0",
"Rev": "0c00cb0a026b71034ebc8205263c77dad3577db5"
},
{
"ImportPath": "github.com/rackspace/gophercloud",
"Comment": "v1.0.0-232-g2e7ab37",
"Rev": "2e7ab378257b8723e02cbceac7410be4db286436"
},
{ {
"ImportPath": "github.com/tent/http-link-go", "ImportPath": "github.com/tent/http-link-go",
"Rev": "ac974c61c2f990f4115b119354b5e0b47550e888" "Rev": "ac974c61c2f990f4115b119354b5e0b47550e888"

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,46 @@
# mapstructure
mapstructure is a Go library for decoding generic map values to structures
and vice versa, while providing helpful error handling.
This library is most useful when decoding values from some data stream (JSON,
Gob, etc.) where you don't _quite_ know the structure of the underlying data
until you read a part of it. You can therefore read a `map[string]interface{}`
and use this library to decode it into the proper underlying native Go
structure.
## Installation
Standard `go get`:
```
$ go get github.com/mitchellh/mapstructure
```
## Usage & Example
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure).
The `Decode` function has examples associated with it there.
## But Why?!
Go offers fantastic standard libraries for decoding formats such as JSON.
The standard method is to have a struct pre-created, and populate that struct
from the bytes of the encoded format. This is great, but the problem is if
you have configuration or an encoding that changes slightly depending on
specific fields. For example, consider this JSON:
```json
{
"type": "person",
"name": "Mitchell"
}
```
Perhaps we can't populate a specific structure without first reading
the "type" field from the JSON. We could always do two passes over the
decoding of the JSON (reading the "type" first, and the rest later).
However, it is much simpler to just decode this into a `map[string]interface{}`
structure, read the "type" key, then use something like this library
to decode it into the proper structure.

View File

@ -0,0 +1,84 @@
package mapstructure
import (
"reflect"
"strconv"
"strings"
)
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
// automatically composes multiple DecodeHookFuncs.
//
// The composed funcs are called in order, with the result of the
// previous transformation.
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
return func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
var err error
for _, f1 := range fs {
data, err = f1(f, t, data)
if err != nil {
return nil, err
}
// Modify the from kind to be correct with the new data
f = getKind(reflect.ValueOf(data))
}
return data, nil
}
}
// StringToSliceHookFunc returns a DecodeHookFunc that converts
// string to []string by splitting on the given sep.
func StringToSliceHookFunc(sep string) DecodeHookFunc {
return func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
if f != reflect.String || t != reflect.Slice {
return data, nil
}
raw := data.(string)
if raw == "" {
return []string{}, nil
}
return strings.Split(raw, sep), nil
}
}
func WeaklyTypedHook(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
dataVal := reflect.ValueOf(data)
switch t {
case reflect.String:
switch f {
case reflect.Bool:
if dataVal.Bool() {
return "1", nil
} else {
return "0", nil
}
case reflect.Float32:
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
case reflect.Int:
return strconv.FormatInt(dataVal.Int(), 10), nil
case reflect.Slice:
dataType := dataVal.Type()
elemKind := dataType.Elem().Kind()
if elemKind == reflect.Uint8 {
return string(dataVal.Interface().([]uint8)), nil
}
case reflect.Uint:
return strconv.FormatUint(dataVal.Uint(), 10), nil
}
}
return data, nil
}

View File

@ -0,0 +1,191 @@
package mapstructure
import (
"errors"
"reflect"
"testing"
)
func TestComposeDecodeHookFunc(t *testing.T) {
f1 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return data.(string) + "foo", nil
}
f2 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return data.(string) + "bar", nil
}
f := ComposeDecodeHookFunc(f1, f2)
result, err := f(reflect.String, reflect.Slice, "")
if err != nil {
t.Fatalf("bad: %s", err)
}
if result.(string) != "foobar" {
t.Fatalf("bad: %#v", result)
}
}
func TestComposeDecodeHookFunc_err(t *testing.T) {
f1 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) {
return nil, errors.New("foo")
}
f2 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) {
panic("NOPE")
}
f := ComposeDecodeHookFunc(f1, f2)
_, err := f(reflect.String, reflect.Slice, 42)
if err.Error() != "foo" {
t.Fatalf("bad: %s", err)
}
}
func TestComposeDecodeHookFunc_kinds(t *testing.T) {
var f2From reflect.Kind
f1 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return int(42), nil
}
f2 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
f2From = f
return data, nil
}
f := ComposeDecodeHookFunc(f1, f2)
_, err := f(reflect.String, reflect.Slice, "")
if err != nil {
t.Fatalf("bad: %s", err)
}
if f2From != reflect.Int {
t.Fatalf("bad: %#v", f2From)
}
}
func TestStringToSliceHookFunc(t *testing.T) {
f := StringToSliceHookFunc(",")
cases := []struct {
f, t reflect.Kind
data interface{}
result interface{}
err bool
}{
{reflect.Slice, reflect.Slice, 42, 42, false},
{reflect.String, reflect.String, 42, 42, false},
{
reflect.String,
reflect.Slice,
"foo,bar,baz",
[]string{"foo", "bar", "baz"},
false,
},
{
reflect.String,
reflect.Slice,
"",
[]string{},
false,
},
}
for i, tc := range cases {
actual, err := f(tc.f, tc.t, tc.data)
if tc.err != (err != nil) {
t.Fatalf("case %d: expected err %#v", i, tc.err)
}
if !reflect.DeepEqual(actual, tc.result) {
t.Fatalf(
"case %d: expected %#v, got %#v",
i, tc.result, actual)
}
}
}
func TestWeaklyTypedHook(t *testing.T) {
var f DecodeHookFunc = WeaklyTypedHook
cases := []struct {
f, t reflect.Kind
data interface{}
result interface{}
err bool
}{
// TO STRING
{
reflect.Bool,
reflect.String,
false,
"0",
false,
},
{
reflect.Bool,
reflect.String,
true,
"1",
false,
},
{
reflect.Float32,
reflect.String,
float32(7),
"7",
false,
},
{
reflect.Int,
reflect.String,
int(7),
"7",
false,
},
{
reflect.Slice,
reflect.String,
[]uint8("foo"),
"foo",
false,
},
{
reflect.Uint,
reflect.String,
uint(7),
"7",
false,
},
}
for i, tc := range cases {
actual, err := f(tc.f, tc.t, tc.data)
if tc.err != (err != nil) {
t.Fatalf("case %d: expected err %#v", i, tc.err)
}
if !reflect.DeepEqual(actual, tc.result) {
t.Fatalf(
"case %d: expected %#v, got %#v",
i, tc.result, actual)
}
}
}

View File

@ -0,0 +1,32 @@
package mapstructure
import (
"fmt"
"strings"
)
// Error implements the error interface and can represents multiple
// errors that occur in the course of a single decode.
type Error struct {
Errors []string
}
func (e *Error) Error() string {
points := make([]string, len(e.Errors))
for i, err := range e.Errors {
points[i] = fmt.Sprintf("* %s", err)
}
return fmt.Sprintf(
"%d error(s) decoding:\n\n%s",
len(e.Errors), strings.Join(points, "\n"))
}
func appendErrors(errors []string, err error) []string {
switch e := err.(type) {
case *Error:
return append(errors, e.Errors...)
default:
return append(errors, e.Error())
}
}

View File

@ -0,0 +1,704 @@
// The mapstructure package exposes functionality to convert an
// abitrary map[string]interface{} into a native Go structure.
//
// The Go structure can be arbitrarily complex, containing slices,
// other structs, etc. and the decoder will properly decode nested
// maps and so on into the proper structures in the native Go struct.
// See the examples to see what the decoder is capable of.
package mapstructure
import (
"errors"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
)
// DecodeHookFunc is the callback function that can be used for
// data transformations. See "DecodeHook" in the DecoderConfig
// struct.
type DecodeHookFunc func(
from reflect.Kind,
to reflect.Kind,
data interface{}) (interface{}, error)
// DecoderConfig is the configuration that is used to create a new decoder
// and allows customization of various aspects of decoding.
type DecoderConfig struct {
// DecodeHook, if set, will be called before any decoding and any
// type conversion (if WeaklyTypedInput is on). This lets you modify
// the values before they're set down onto the resulting struct.
//
// If an error is returned, the entire decode will fail with that
// error.
DecodeHook DecodeHookFunc
// If ErrorUnused is true, then it is an error for there to exist
// keys in the original map that were unused in the decoding process
// (extra keys).
ErrorUnused bool
// If WeaklyTypedInput is true, the decoder will make the following
// "weak" conversions:
//
// - bools to string (true = "1", false = "0")
// - numbers to string (base 10)
// - bools to int/uint (true = 1, false = 0)
// - strings to int/uint (base implied by prefix)
// - int to bool (true if value != 0)
// - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F,
// FALSE, false, False. Anything else is an error)
// - empty array = empty map and vice versa
//
WeaklyTypedInput bool
// Metadata is the struct that will contain extra metadata about
// the decoding. If this is nil, then no metadata will be tracked.
Metadata *Metadata
// Result is a pointer to the struct that will contain the decoded
// value.
Result interface{}
// The tag name that mapstructure reads for field names. This
// defaults to "mapstructure"
TagName string
}
// A Decoder takes a raw interface value and turns it into structured
// data, keeping track of rich error information along the way in case
// anything goes wrong. Unlike the basic top-level Decode method, you can
// more finely control how the Decoder behaves using the DecoderConfig
// structure. The top-level Decode method is just a convenience that sets
// up the most basic Decoder.
type Decoder struct {
config *DecoderConfig
}
// Metadata contains information about decoding a structure that
// is tedious or difficult to get otherwise.
type Metadata struct {
// Keys are the keys of the structure which were successfully decoded
Keys []string
// Unused is a slice of keys that were found in the raw value but
// weren't decoded since there was no matching field in the result interface
Unused []string
}
// Decode takes a map and uses reflection to convert it into the
// given Go native structure. val must be a pointer to a struct.
func Decode(m interface{}, rawVal interface{}) error {
config := &DecoderConfig{
Metadata: nil,
Result: rawVal,
}
decoder, err := NewDecoder(config)
if err != nil {
return err
}
return decoder.Decode(m)
}
// WeakDecode is the same as Decode but is shorthand to enable
// WeaklyTypedInput. See DecoderConfig for more info.
func WeakDecode(input, output interface{}) error {
config := &DecoderConfig{
Metadata: nil,
Result: output,
WeaklyTypedInput: true,
}
decoder, err := NewDecoder(config)
if err != nil {
return err
}
return decoder.Decode(input)
}
// NewDecoder returns a new decoder for the given configuration. Once
// a decoder has been returned, the same configuration must not be used
// again.
func NewDecoder(config *DecoderConfig) (*Decoder, error) {
val := reflect.ValueOf(config.Result)
if val.Kind() != reflect.Ptr {
return nil, errors.New("result must be a pointer")
}
val = val.Elem()
if !val.CanAddr() {
return nil, errors.New("result must be addressable (a pointer)")
}
if config.Metadata != nil {
if config.Metadata.Keys == nil {
config.Metadata.Keys = make([]string, 0)
}
if config.Metadata.Unused == nil {
config.Metadata.Unused = make([]string, 0)
}
}
if config.TagName == "" {
config.TagName = "mapstructure"
}
result := &Decoder{
config: config,
}
return result, nil
}
// Decode decodes the given raw interface to the target pointer specified
// by the configuration.
func (d *Decoder) Decode(raw interface{}) error {
return d.decode("", raw, reflect.ValueOf(d.config.Result).Elem())
}
// Decodes an unknown data type into a specific reflection value.
func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error {
if data == nil {
// If the data is nil, then we don't set anything.
return nil
}
dataVal := reflect.ValueOf(data)
if !dataVal.IsValid() {
// If the data value is invalid, then we just set the value
// to be the zero value.
val.Set(reflect.Zero(val.Type()))
return nil
}
if d.config.DecodeHook != nil {
// We have a DecodeHook, so let's pre-process the data.
var err error
data, err = d.config.DecodeHook(getKind(dataVal), getKind(val), data)
if err != nil {
return err
}
}
var err error
dataKind := getKind(val)
switch dataKind {
case reflect.Bool:
err = d.decodeBool(name, data, val)
case reflect.Interface:
err = d.decodeBasic(name, data, val)
case reflect.String:
err = d.decodeString(name, data, val)
case reflect.Int:
err = d.decodeInt(name, data, val)
case reflect.Uint:
err = d.decodeUint(name, data, val)
case reflect.Float32:
err = d.decodeFloat(name, data, val)
case reflect.Struct:
err = d.decodeStruct(name, data, val)
case reflect.Map:
err = d.decodeMap(name, data, val)
case reflect.Ptr:
err = d.decodePtr(name, data, val)
case reflect.Slice:
err = d.decodeSlice(name, data, val)
default:
// If we reached this point then we weren't able to decode it
return fmt.Errorf("%s: unsupported type: %s", name, dataKind)
}
// If we reached here, then we successfully decoded SOMETHING, so
// mark the key as used if we're tracking metadata.
if d.config.Metadata != nil && name != "" {
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
}
return err
}
// This decodes a basic type (bool, int, string, etc.) and sets the
// value to "data" of that type.
func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataValType := dataVal.Type()
if !dataValType.AssignableTo(val.Type()) {
return fmt.Errorf(
"'%s' expected type '%s', got '%s'",
name, val.Type(), dataValType)
}
val.Set(dataVal)
return nil
}
func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := getKind(dataVal)
converted := true
switch {
case dataKind == reflect.String:
val.SetString(dataVal.String())
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
if dataVal.Bool() {
val.SetString("1")
} else {
val.SetString("0")
}
case dataKind == reflect.Int && d.config.WeaklyTypedInput:
val.SetString(strconv.FormatInt(dataVal.Int(), 10))
case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64))
case dataKind == reflect.Slice && d.config.WeaklyTypedInput:
dataType := dataVal.Type()
elemKind := dataType.Elem().Kind()
switch {
case elemKind == reflect.Uint8:
val.SetString(string(dataVal.Interface().([]uint8)))
default:
converted = false
}
default:
converted = false
}
if !converted {
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type())
}
return nil
}
func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := getKind(dataVal)
switch {
case dataKind == reflect.Int:
val.SetInt(dataVal.Int())
case dataKind == reflect.Uint:
val.SetInt(int64(dataVal.Uint()))
case dataKind == reflect.Float32:
val.SetInt(int64(dataVal.Float()))
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
if dataVal.Bool() {
val.SetInt(1)
} else {
val.SetInt(0)
}
case dataKind == reflect.String && d.config.WeaklyTypedInput:
i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits())
if err == nil {
val.SetInt(i)
} else {
return fmt.Errorf("cannot parse '%s' as int: %s", name, err)
}
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type())
}
return nil
}
func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := getKind(dataVal)
switch {
case dataKind == reflect.Int:
val.SetUint(uint64(dataVal.Int()))
case dataKind == reflect.Uint:
val.SetUint(dataVal.Uint())
case dataKind == reflect.Float32:
val.SetUint(uint64(dataVal.Float()))
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
if dataVal.Bool() {
val.SetUint(1)
} else {
val.SetUint(0)
}
case dataKind == reflect.String && d.config.WeaklyTypedInput:
i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits())
if err == nil {
val.SetUint(i)
} else {
return fmt.Errorf("cannot parse '%s' as uint: %s", name, err)
}
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type())
}
return nil
}
func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := getKind(dataVal)
switch {
case dataKind == reflect.Bool:
val.SetBool(dataVal.Bool())
case dataKind == reflect.Int && d.config.WeaklyTypedInput:
val.SetBool(dataVal.Int() != 0)
case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
val.SetBool(dataVal.Uint() != 0)
case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
val.SetBool(dataVal.Float() != 0)
case dataKind == reflect.String && d.config.WeaklyTypedInput:
b, err := strconv.ParseBool(dataVal.String())
if err == nil {
val.SetBool(b)
} else if dataVal.String() == "" {
val.SetBool(false)
} else {
return fmt.Errorf("cannot parse '%s' as bool: %s", name, err)
}
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type())
}
return nil
}
func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := getKind(dataVal)
switch {
case dataKind == reflect.Int:
val.SetFloat(float64(dataVal.Int()))
case dataKind == reflect.Uint:
val.SetFloat(float64(dataVal.Uint()))
case dataKind == reflect.Float32:
val.SetFloat(float64(dataVal.Float()))
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
if dataVal.Bool() {
val.SetFloat(1)
} else {
val.SetFloat(0)
}
case dataKind == reflect.String && d.config.WeaklyTypedInput:
f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits())
if err == nil {
val.SetFloat(f)
} else {
return fmt.Errorf("cannot parse '%s' as float: %s", name, err)
}
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type())
}
return nil
}
func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error {
valType := val.Type()
valKeyType := valType.Key()
valElemType := valType.Elem()
// Make a new map to hold our result
mapType := reflect.MapOf(valKeyType, valElemType)
valMap := reflect.MakeMap(mapType)
// Check input type
dataVal := reflect.Indirect(reflect.ValueOf(data))
if dataVal.Kind() != reflect.Map {
// Accept empty array/slice instead of an empty map in weakly typed mode
if d.config.WeaklyTypedInput &&
(dataVal.Kind() == reflect.Slice || dataVal.Kind() == reflect.Array) &&
dataVal.Len() == 0 {
val.Set(valMap)
return nil
} else {
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
}
}
// Accumulate errors
errors := make([]string, 0)
for _, k := range dataVal.MapKeys() {
fieldName := fmt.Sprintf("%s[%s]", name, k)
// First decode the key into the proper type
currentKey := reflect.Indirect(reflect.New(valKeyType))
if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
errors = appendErrors(errors, err)
continue
}
// Next decode the data into the proper type
v := dataVal.MapIndex(k).Interface()
currentVal := reflect.Indirect(reflect.New(valElemType))
if err := d.decode(fieldName, v, currentVal); err != nil {
errors = appendErrors(errors, err)
continue
}
valMap.SetMapIndex(currentKey, currentVal)
}
// Set the built up map to the value
val.Set(valMap)
// If we had errors, return those
if len(errors) > 0 {
return &Error{errors}
}
return nil
}
func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error {
// Create an element of the concrete (non pointer) type and decode
// into that. Then set the value of the pointer to this type.
valType := val.Type()
valElemType := valType.Elem()
realVal := reflect.New(valElemType)
if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
return err
}
val.Set(realVal)
return nil
}
func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.Indirect(reflect.ValueOf(data))
dataValKind := dataVal.Kind()
valType := val.Type()
valElemType := valType.Elem()
sliceType := reflect.SliceOf(valElemType)
// Check input type
if dataValKind != reflect.Array && dataValKind != reflect.Slice {
// Accept empty map instead of array/slice in weakly typed mode
if d.config.WeaklyTypedInput && dataVal.Kind() == reflect.Map && dataVal.Len() == 0 {
val.Set(reflect.MakeSlice(sliceType, 0, 0))
return nil
} else {
return fmt.Errorf(
"'%s': source data must be an array or slice, got %s", name, dataValKind)
}
}
// Make a new slice to hold our result, same size as the original data.
valSlice := reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len())
// Accumulate any errors
errors := make([]string, 0)
for i := 0; i < dataVal.Len(); i++ {
currentData := dataVal.Index(i).Interface()
currentField := valSlice.Index(i)
fieldName := fmt.Sprintf("%s[%d]", name, i)
if err := d.decode(fieldName, currentData, currentField); err != nil {
errors = appendErrors(errors, err)
}
}
// Finally, set the value to the slice we built up
val.Set(valSlice)
// If there were errors, we return those
if len(errors) > 0 {
return &Error{errors}
}
return nil
}
func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.Indirect(reflect.ValueOf(data))
dataValKind := dataVal.Kind()
if dataValKind != reflect.Map {
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataValKind)
}
dataValType := dataVal.Type()
if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface {
return fmt.Errorf(
"'%s' needs a map with string keys, has '%s' keys",
name, dataValType.Key().Kind())
}
dataValKeys := make(map[reflect.Value]struct{})
dataValKeysUnused := make(map[interface{}]struct{})
for _, dataValKey := range dataVal.MapKeys() {
dataValKeys[dataValKey] = struct{}{}
dataValKeysUnused[dataValKey.Interface()] = struct{}{}
}
errors := make([]string, 0)
// This slice will keep track of all the structs we'll be decoding.
// There can be more than one struct if there are embedded structs
// that are squashed.
structs := make([]reflect.Value, 1, 5)
structs[0] = val
// Compile the list of all the fields that we're going to be decoding
// from all the structs.
fields := make(map[*reflect.StructField]reflect.Value)
for len(structs) > 0 {
structVal := structs[0]
structs = structs[1:]
structType := structVal.Type()
for i := 0; i < structType.NumField(); i++ {
fieldType := structType.Field(i)
if fieldType.Anonymous {
fieldKind := fieldType.Type.Kind()
if fieldKind != reflect.Struct {
errors = appendErrors(errors,
fmt.Errorf("%s: unsupported type: %s", fieldType.Name, fieldKind))
continue
}
// We have an embedded field. We "squash" the fields down
// if specified in the tag.
squash := false
tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
for _, tag := range tagParts[1:] {
if tag == "squash" {
squash = true
break
}
}
if squash {
structs = append(structs, val.FieldByName(fieldType.Name))
continue
}
}
// Normal struct field, store it away
fields[&fieldType] = structVal.Field(i)
}
}
for fieldType, field := range fields {
fieldName := fieldType.Name
tagValue := fieldType.Tag.Get(d.config.TagName)
tagValue = strings.SplitN(tagValue, ",", 2)[0]
if tagValue != "" {
fieldName = tagValue
}
rawMapKey := reflect.ValueOf(fieldName)
rawMapVal := dataVal.MapIndex(rawMapKey)
if !rawMapVal.IsValid() {
// Do a slower search by iterating over each key and
// doing case-insensitive search.
for dataValKey, _ := range dataValKeys {
mK, ok := dataValKey.Interface().(string)
if !ok {
// Not a string key
continue
}
if strings.EqualFold(mK, fieldName) {
rawMapKey = dataValKey
rawMapVal = dataVal.MapIndex(dataValKey)
break
}
}
if !rawMapVal.IsValid() {
// There was no matching key in the map for the value in
// the struct. Just ignore.
continue
}
}
// Delete the key we're using from the unused map so we stop tracking
delete(dataValKeysUnused, rawMapKey.Interface())
if !field.IsValid() {
// This should never happen
panic("field is not valid")
}
// If we can't set the field, then it is unexported or something,
// and we just continue onwards.
if !field.CanSet() {
continue
}
// If the name is empty string, then we're at the root, and we
// don't dot-join the fields.
if name != "" {
fieldName = fmt.Sprintf("%s.%s", name, fieldName)
}
if err := d.decode(fieldName, rawMapVal.Interface(), field); err != nil {
errors = appendErrors(errors, err)
}
}
if d.config.ErrorUnused && len(dataValKeysUnused) > 0 {
keys := make([]string, 0, len(dataValKeysUnused))
for rawKey, _ := range dataValKeysUnused {
keys = append(keys, rawKey.(string))
}
sort.Strings(keys)
err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", "))
errors = appendErrors(errors, err)
}
if len(errors) > 0 {
return &Error{errors}
}
// Add the unused keys to the list of unused keys if we're tracking metadata
if d.config.Metadata != nil {
for rawKey, _ := range dataValKeysUnused {
key := rawKey.(string)
if name != "" {
key = fmt.Sprintf("%s.%s", name, key)
}
d.config.Metadata.Unused = append(d.config.Metadata.Unused, key)
}
}
return nil
}
func getKind(val reflect.Value) reflect.Kind {
kind := val.Kind()
switch {
case kind >= reflect.Int && kind <= reflect.Int64:
return reflect.Int
case kind >= reflect.Uint && kind <= reflect.Uint64:
return reflect.Uint
case kind >= reflect.Float32 && kind <= reflect.Float64:
return reflect.Float32
default:
return kind
}
}

View File

@ -0,0 +1,243 @@
package mapstructure
import (
"testing"
)
func Benchmark_Decode(b *testing.B) {
type Person struct {
Name string
Age int
Emails []string
Extra map[string]string
}
input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"emails": []string{"one", "two", "three"},
"extra": map[string]string{
"twitter": "mitchellh",
},
}
var result Person
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
func Benchmark_DecodeBasic(b *testing.B) {
input := map[string]interface{}{
"vstring": "foo",
"vint": 42,
"Vuint": 42,
"vbool": true,
"Vfloat": 42.42,
"vsilent": true,
"vdata": 42,
}
var result Basic
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
func Benchmark_DecodeEmbedded(b *testing.B) {
input := map[string]interface{}{
"vstring": "foo",
"Basic": map[string]interface{}{
"vstring": "innerfoo",
},
"vunique": "bar",
}
var result Embedded
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
func Benchmark_DecodeTypeConversion(b *testing.B) {
input := map[string]interface{}{
"IntToFloat": 42,
"IntToUint": 42,
"IntToBool": 1,
"IntToString": 42,
"UintToInt": 42,
"UintToFloat": 42,
"UintToBool": 42,
"UintToString": 42,
"BoolToInt": true,
"BoolToUint": true,
"BoolToFloat": true,
"BoolToString": true,
"FloatToInt": 42.42,
"FloatToUint": 42.42,
"FloatToBool": 42.42,
"FloatToString": 42.42,
"StringToInt": "42",
"StringToUint": "42",
"StringToBool": "1",
"StringToFloat": "42.42",
"SliceToMap": []interface{}{},
"MapToSlice": map[string]interface{}{},
}
var resultStrict TypeConversionResult
for i := 0; i < b.N; i++ {
Decode(input, &resultStrict)
}
}
func Benchmark_DecodeMap(b *testing.B) {
input := map[string]interface{}{
"vfoo": "foo",
"vother": map[interface{}]interface{}{
"foo": "foo",
"bar": "bar",
},
}
var result Map
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
func Benchmark_DecodeMapOfStruct(b *testing.B) {
input := map[string]interface{}{
"value": map[string]interface{}{
"foo": map[string]string{"vstring": "one"},
"bar": map[string]string{"vstring": "two"},
},
}
var result MapOfStruct
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
func Benchmark_DecodeSlice(b *testing.B) {
input := map[string]interface{}{
"vfoo": "foo",
"vbar": []string{"foo", "bar", "baz"},
}
var result Slice
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
func Benchmark_DecodeSliceOfStruct(b *testing.B) {
input := map[string]interface{}{
"value": []map[string]interface{}{
{"vstring": "one"},
{"vstring": "two"},
},
}
var result SliceOfStruct
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
func Benchmark_DecodeWeaklyTypedInput(b *testing.B) {
type Person struct {
Name string
Age int
Emails []string
}
// This input can come from anywhere, but typically comes from
// something like decoding JSON, generated by a weakly typed language
// such as PHP.
input := map[string]interface{}{
"name": 123, // number => string
"age": "42", // string => number
"emails": map[string]interface{}{}, // empty map => empty array
}
var result Person
config := &DecoderConfig{
WeaklyTypedInput: true,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
panic(err)
}
for i := 0; i < b.N; i++ {
decoder.Decode(input)
}
}
func Benchmark_DecodeMetadata(b *testing.B) {
type Person struct {
Name string
Age int
}
input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"email": "foo@bar.com",
}
var md Metadata
var result Person
config := &DecoderConfig{
Metadata: &md,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
panic(err)
}
for i := 0; i < b.N; i++ {
decoder.Decode(input)
}
}
func Benchmark_DecodeMetadataEmbedded(b *testing.B) {
input := map[string]interface{}{
"vstring": "foo",
"vunique": "bar",
}
var md Metadata
var result EmbeddedSquash
config := &DecoderConfig{
Metadata: &md,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
b.Fatalf("err: %s", err)
}
for i := 0; i < b.N; i++ {
decoder.Decode(input)
}
}
func Benchmark_DecodeTagged(b *testing.B) {
input := map[string]interface{}{
"foo": "bar",
"bar": "value",
}
var result Tagged
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}

View File

@ -0,0 +1,47 @@
package mapstructure
import "testing"
// GH-1
func TestDecode_NilValue(t *testing.T) {
input := map[string]interface{}{
"vfoo": nil,
"vother": nil,
}
var result Map
err := Decode(input, &result)
if err != nil {
t.Fatalf("should not error: %s", err)
}
if result.Vfoo != "" {
t.Fatalf("value should be default: %s", result.Vfoo)
}
if result.Vother != nil {
t.Fatalf("Vother should be nil: %s", result.Vother)
}
}
// GH-10
func TestDecode_mapInterfaceInterface(t *testing.T) {
input := map[interface{}]interface{}{
"vfoo": nil,
"vother": nil,
}
var result Map
err := Decode(input, &result)
if err != nil {
t.Fatalf("should not error: %s", err)
}
if result.Vfoo != "" {
t.Fatalf("value should be default: %s", result.Vfoo)
}
if result.Vother != nil {
t.Fatalf("Vother should be nil: %s", result.Vother)
}
}

View File

@ -0,0 +1,169 @@
package mapstructure
import (
"fmt"
)
func ExampleDecode() {
type Person struct {
Name string
Age int
Emails []string
Extra map[string]string
}
// This input can come from anywhere, but typically comes from
// something like decoding JSON where we're not quite sure of the
// struct initially.
input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"emails": []string{"one", "two", "three"},
"extra": map[string]string{
"twitter": "mitchellh",
},
}
var result Person
err := Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// Output:
// mapstructure.Person{Name:"Mitchell", Age:91, Emails:[]string{"one", "two", "three"}, Extra:map[string]string{"twitter":"mitchellh"}}
}
func ExampleDecode_errors() {
type Person struct {
Name string
Age int
Emails []string
Extra map[string]string
}
// This input can come from anywhere, but typically comes from
// something like decoding JSON where we're not quite sure of the
// struct initially.
input := map[string]interface{}{
"name": 123,
"age": "bad value",
"emails": []int{1, 2, 3},
}
var result Person
err := Decode(input, &result)
if err == nil {
panic("should have an error")
}
fmt.Println(err.Error())
// Output:
// 5 error(s) decoding:
//
// * 'Name' expected type 'string', got unconvertible type 'int'
// * 'Age' expected type 'int', got unconvertible type 'string'
// * 'Emails[0]' expected type 'string', got unconvertible type 'int'
// * 'Emails[1]' expected type 'string', got unconvertible type 'int'
// * 'Emails[2]' expected type 'string', got unconvertible type 'int'
}
func ExampleDecode_metadata() {
type Person struct {
Name string
Age int
}
// This input can come from anywhere, but typically comes from
// something like decoding JSON where we're not quite sure of the
// struct initially.
input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"email": "foo@bar.com",
}
// For metadata, we make a more advanced DecoderConfig so we can
// more finely configure the decoder that is used. In this case, we
// just tell the decoder we want to track metadata.
var md Metadata
var result Person
config := &DecoderConfig{
Metadata: &md,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
panic(err)
}
if err := decoder.Decode(input); err != nil {
panic(err)
}
fmt.Printf("Unused keys: %#v", md.Unused)
// Output:
// Unused keys: []string{"email"}
}
func ExampleDecode_weaklyTypedInput() {
type Person struct {
Name string
Age int
Emails []string
}
// This input can come from anywhere, but typically comes from
// something like decoding JSON, generated by a weakly typed language
// such as PHP.
input := map[string]interface{}{
"name": 123, // number => string
"age": "42", // string => number
"emails": map[string]interface{}{}, // empty map => empty array
}
var result Person
config := &DecoderConfig{
WeaklyTypedInput: true,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
panic(err)
}
err = decoder.Decode(input)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// Output: mapstructure.Person{Name:"123", Age:42, Emails:[]string{}}
}
func ExampleDecode_tags() {
// Note that the mapstructure tags defined in the struct type
// can indicate which fields the values are mapped to.
type Person struct {
Name string `mapstructure:"person_name"`
Age int `mapstructure:"person_age"`
}
input := map[string]interface{}{
"person_name": "Mitchell",
"person_age": 91,
}
var result Person
err := Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// Output:
// mapstructure.Person{Name:"Mitchell", Age:91}
}

View File

@ -0,0 +1,828 @@
package mapstructure
import (
"reflect"
"sort"
"testing"
)
type Basic struct {
Vstring string
Vint int
Vuint uint
Vbool bool
Vfloat float64
Vextra string
vsilent bool
Vdata interface{}
}
type Embedded struct {
Basic
Vunique string
}
type EmbeddedPointer struct {
*Basic
Vunique string
}
type EmbeddedSquash struct {
Basic `mapstructure:",squash"`
Vunique string
}
type Map struct {
Vfoo string
Vother map[string]string
}
type MapOfStruct struct {
Value map[string]Basic
}
type Nested struct {
Vfoo string
Vbar Basic
}
type NestedPointer struct {
Vfoo string
Vbar *Basic
}
type Slice struct {
Vfoo string
Vbar []string
}
type SliceOfStruct struct {
Value []Basic
}
type Tagged struct {
Extra string `mapstructure:"bar,what,what"`
Value string `mapstructure:"foo"`
}
type TypeConversionResult struct {
IntToFloat float32
IntToUint uint
IntToBool bool
IntToString string
UintToInt int
UintToFloat float32
UintToBool bool
UintToString string
BoolToInt int
BoolToUint uint
BoolToFloat float32
BoolToString string
FloatToInt int
FloatToUint uint
FloatToBool bool
FloatToString string
SliceUint8ToString string
StringToInt int
StringToUint uint
StringToBool bool
StringToFloat float32
SliceToMap map[string]interface{}
MapToSlice []interface{}
}
func TestBasicTypes(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vstring": "foo",
"vint": 42,
"Vuint": 42,
"vbool": true,
"Vfloat": 42.42,
"vsilent": true,
"vdata": 42,
}
var result Basic
err := Decode(input, &result)
if err != nil {
t.Errorf("got an err: %s", err.Error())
t.FailNow()
}
if result.Vstring != "foo" {
t.Errorf("vstring value should be 'foo': %#v", result.Vstring)
}
if result.Vint != 42 {
t.Errorf("vint value should be 42: %#v", result.Vint)
}
if result.Vuint != 42 {
t.Errorf("vuint value should be 42: %#v", result.Vuint)
}
if result.Vbool != true {
t.Errorf("vbool value should be true: %#v", result.Vbool)
}
if result.Vfloat != 42.42 {
t.Errorf("vfloat value should be 42.42: %#v", result.Vfloat)
}
if result.Vextra != "" {
t.Errorf("vextra value should be empty: %#v", result.Vextra)
}
if result.vsilent != false {
t.Error("vsilent should not be set, it is unexported")
}
if result.Vdata != 42 {
t.Error("vdata should be valid")
}
}
func TestBasic_IntWithFloat(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vint": float64(42),
}
var result Basic
err := Decode(input, &result)
if err != nil {
t.Fatalf("got an err: %s", err)
}
}
func TestDecode_Embedded(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vstring": "foo",
"Basic": map[string]interface{}{
"vstring": "innerfoo",
},
"vunique": "bar",
}
var result Embedded
err := Decode(input, &result)
if err != nil {
t.Fatalf("got an err: %s", err.Error())
}
if result.Vstring != "innerfoo" {
t.Errorf("vstring value should be 'innerfoo': %#v", result.Vstring)
}
if result.Vunique != "bar" {
t.Errorf("vunique value should be 'bar': %#v", result.Vunique)
}
}
func TestDecode_EmbeddedPointer(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vstring": "foo",
"Basic": map[string]interface{}{
"vstring": "innerfoo",
},
"vunique": "bar",
}
var result EmbeddedPointer
err := Decode(input, &result)
if err == nil {
t.Fatal("should get error")
}
}
func TestDecode_EmbeddedSquash(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vstring": "foo",
"vunique": "bar",
}
var result EmbeddedSquash
err := Decode(input, &result)
if err != nil {
t.Fatalf("got an err: %s", err.Error())
}
if result.Vstring != "foo" {
t.Errorf("vstring value should be 'foo': %#v", result.Vstring)
}
if result.Vunique != "bar" {
t.Errorf("vunique value should be 'bar': %#v", result.Vunique)
}
}
func TestDecode_DecodeHook(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vint": "WHAT",
}
decodeHook := func(from reflect.Kind, to reflect.Kind, v interface{}) (interface{}, error) {
if from == reflect.String && to != reflect.String {
return 5, nil
}
return v, nil
}
var result Basic
config := &DecoderConfig{
DecodeHook: decodeHook,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
t.Fatalf("err: %s", err)
}
err = decoder.Decode(input)
if err != nil {
t.Fatalf("got an err: %s", err)
}
if result.Vint != 5 {
t.Errorf("vint should be 5: %#v", result.Vint)
}
}
func TestDecode_Nil(t *testing.T) {
t.Parallel()
var input interface{} = nil
result := Basic{
Vstring: "foo",
}
err := Decode(input, &result)
if err != nil {
t.Fatalf("err: %s", err)
}
if result.Vstring != "foo" {
t.Fatalf("bad: %#v", result.Vstring)
}
}
func TestDecode_NonStruct(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"foo": "bar",
"bar": "baz",
}
var result map[string]string
err := Decode(input, &result)
if err != nil {
t.Fatalf("err: %s", err)
}
if result["foo"] != "bar" {
t.Fatal("foo is not bar")
}
}
func TestDecode_TypeConversion(t *testing.T) {
input := map[string]interface{}{
"IntToFloat": 42,
"IntToUint": 42,
"IntToBool": 1,
"IntToString": 42,
"UintToInt": 42,
"UintToFloat": 42,
"UintToBool": 42,
"UintToString": 42,
"BoolToInt": true,
"BoolToUint": true,
"BoolToFloat": true,
"BoolToString": true,
"FloatToInt": 42.42,
"FloatToUint": 42.42,
"FloatToBool": 42.42,
"FloatToString": 42.42,
"SliceUint8ToString": []uint8("foo"),
"StringToInt": "42",
"StringToUint": "42",
"StringToBool": "1",
"StringToFloat": "42.42",
"SliceToMap": []interface{}{},
"MapToSlice": map[string]interface{}{},
}
expectedResultStrict := TypeConversionResult{
IntToFloat: 42.0,
IntToUint: 42,
UintToInt: 42,
UintToFloat: 42,
BoolToInt: 0,
BoolToUint: 0,
BoolToFloat: 0,
FloatToInt: 42,
FloatToUint: 42,
}
expectedResultWeak := TypeConversionResult{
IntToFloat: 42.0,
IntToUint: 42,
IntToBool: true,
IntToString: "42",
UintToInt: 42,
UintToFloat: 42,
UintToBool: true,
UintToString: "42",
BoolToInt: 1,
BoolToUint: 1,
BoolToFloat: 1,
BoolToString: "1",
FloatToInt: 42,
FloatToUint: 42,
FloatToBool: true,
FloatToString: "42.42",
SliceUint8ToString: "foo",
StringToInt: 42,
StringToUint: 42,
StringToBool: true,
StringToFloat: 42.42,
SliceToMap: map[string]interface{}{},
MapToSlice: []interface{}{},
}
// Test strict type conversion
var resultStrict TypeConversionResult
err := Decode(input, &resultStrict)
if err == nil {
t.Errorf("should return an error")
}
if !reflect.DeepEqual(resultStrict, expectedResultStrict) {
t.Errorf("expected %v, got: %v", expectedResultStrict, resultStrict)
}
// Test weak type conversion
var decoder *Decoder
var resultWeak TypeConversionResult
config := &DecoderConfig{
WeaklyTypedInput: true,
Result: &resultWeak,
}
decoder, err = NewDecoder(config)
if err != nil {
t.Fatalf("err: %s", err)
}
err = decoder.Decode(input)
if err != nil {
t.Fatalf("got an err: %s", err)
}
if !reflect.DeepEqual(resultWeak, expectedResultWeak) {
t.Errorf("expected \n%#v, got: \n%#v", expectedResultWeak, resultWeak)
}
}
func TestDecoder_ErrorUnused(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vstring": "hello",
"foo": "bar",
}
var result Basic
config := &DecoderConfig{
ErrorUnused: true,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
t.Fatalf("err: %s", err)
}
err = decoder.Decode(input)
if err == nil {
t.Fatal("expected error")
}
}
func TestMap(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vfoo": "foo",
"vother": map[interface{}]interface{}{
"foo": "foo",
"bar": "bar",
},
}
var result Map
err := Decode(input, &result)
if err != nil {
t.Fatalf("got an error: %s", err)
}
if result.Vfoo != "foo" {
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
}
if result.Vother == nil {
t.Fatal("vother should not be nil")
}
if len(result.Vother) != 2 {
t.Error("vother should have two items")
}
if result.Vother["foo"] != "foo" {
t.Errorf("'foo' key should be foo, got: %#v", result.Vother["foo"])
}
if result.Vother["bar"] != "bar" {
t.Errorf("'bar' key should be bar, got: %#v", result.Vother["bar"])
}
}
func TestMapOfStruct(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"value": map[string]interface{}{
"foo": map[string]string{"vstring": "one"},
"bar": map[string]string{"vstring": "two"},
},
}
var result MapOfStruct
err := Decode(input, &result)
if err != nil {
t.Fatalf("got an err: %s", err)
}
if result.Value == nil {
t.Fatal("value should not be nil")
}
if len(result.Value) != 2 {
t.Error("value should have two items")
}
if result.Value["foo"].Vstring != "one" {
t.Errorf("foo value should be 'one', got: %s", result.Value["foo"].Vstring)
}
if result.Value["bar"].Vstring != "two" {
t.Errorf("bar value should be 'two', got: %s", result.Value["bar"].Vstring)
}
}
func TestNestedType(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vfoo": "foo",
"vbar": map[string]interface{}{
"vstring": "foo",
"vint": 42,
"vbool": true,
},
}
var result Nested
err := Decode(input, &result)
if err != nil {
t.Fatalf("got an err: %s", err.Error())
}
if result.Vfoo != "foo" {
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
}
if result.Vbar.Vstring != "foo" {
t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
}
if result.Vbar.Vint != 42 {
t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
}
if result.Vbar.Vbool != true {
t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
}
if result.Vbar.Vextra != "" {
t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
}
}
func TestNestedTypePointer(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vfoo": "foo",
"vbar": &map[string]interface{}{
"vstring": "foo",
"vint": 42,
"vbool": true,
},
}
var result NestedPointer
err := Decode(input, &result)
if err != nil {
t.Fatalf("got an err: %s", err.Error())
}
if result.Vfoo != "foo" {
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
}
if result.Vbar.Vstring != "foo" {
t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
}
if result.Vbar.Vint != 42 {
t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
}
if result.Vbar.Vbool != true {
t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
}
if result.Vbar.Vextra != "" {
t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
}
}
func TestSlice(t *testing.T) {
t.Parallel()
inputStringSlice := map[string]interface{}{
"vfoo": "foo",
"vbar": []string{"foo", "bar", "baz"},
}
inputStringSlicePointer := map[string]interface{}{
"vfoo": "foo",
"vbar": &[]string{"foo", "bar", "baz"},
}
outputStringSlice := &Slice{
"foo",
[]string{"foo", "bar", "baz"},
}
testSliceInput(t, inputStringSlice, outputStringSlice)
testSliceInput(t, inputStringSlicePointer, outputStringSlice)
}
func TestInvalidSlice(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vfoo": "foo",
"vbar": 42,
}
result := Slice{}
err := Decode(input, &result)
if err == nil {
t.Errorf("expected failure")
}
}
func TestSliceOfStruct(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"value": []map[string]interface{}{
{"vstring": "one"},
{"vstring": "two"},
},
}
var result SliceOfStruct
err := Decode(input, &result)
if err != nil {
t.Fatalf("got unexpected error: %s", err)
}
if len(result.Value) != 2 {
t.Fatalf("expected two values, got %d", len(result.Value))
}
if result.Value[0].Vstring != "one" {
t.Errorf("first value should be 'one', got: %s", result.Value[0].Vstring)
}
if result.Value[1].Vstring != "two" {
t.Errorf("second value should be 'two', got: %s", result.Value[1].Vstring)
}
}
func TestInvalidType(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vstring": 42,
}
var result Basic
err := Decode(input, &result)
if err == nil {
t.Fatal("error should exist")
}
derr, ok := err.(*Error)
if !ok {
t.Fatalf("error should be kind of Error, instead: %#v", err)
}
if derr.Errors[0] != "'Vstring' expected type 'string', got unconvertible type 'int'" {
t.Errorf("got unexpected error: %s", err)
}
}
func TestMetadata(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vfoo": "foo",
"vbar": map[string]interface{}{
"vstring": "foo",
"Vuint": 42,
"foo": "bar",
},
"bar": "nil",
}
var md Metadata
var result Nested
config := &DecoderConfig{
Metadata: &md,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
t.Fatalf("err: %s", err)
}
err = decoder.Decode(input)
if err != nil {
t.Fatalf("err: %s", err.Error())
}
expectedKeys := []string{"Vfoo", "Vbar.Vstring", "Vbar.Vuint", "Vbar"}
if !reflect.DeepEqual(md.Keys, expectedKeys) {
t.Fatalf("bad keys: %#v", md.Keys)
}
expectedUnused := []string{"Vbar.foo", "bar"}
if !reflect.DeepEqual(md.Unused, expectedUnused) {
t.Fatalf("bad unused: %#v", md.Unused)
}
}
func TestMetadata_Embedded(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vstring": "foo",
"vunique": "bar",
}
var md Metadata
var result EmbeddedSquash
config := &DecoderConfig{
Metadata: &md,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
t.Fatalf("err: %s", err)
}
err = decoder.Decode(input)
if err != nil {
t.Fatalf("err: %s", err.Error())
}
expectedKeys := []string{"Vstring", "Vunique"}
sort.Strings(md.Keys)
if !reflect.DeepEqual(md.Keys, expectedKeys) {
t.Fatalf("bad keys: %#v", md.Keys)
}
expectedUnused := []string{}
if !reflect.DeepEqual(md.Unused, expectedUnused) {
t.Fatalf("bad unused: %#v", md.Unused)
}
}
func TestNonPtrValue(t *testing.T) {
t.Parallel()
err := Decode(map[string]interface{}{}, Basic{})
if err == nil {
t.Fatal("error should exist")
}
if err.Error() != "result must be a pointer" {
t.Errorf("got unexpected error: %s", err)
}
}
func TestTagged(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"foo": "bar",
"bar": "value",
}
var result Tagged
err := Decode(input, &result)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if result.Value != "bar" {
t.Errorf("value should be 'bar', got: %#v", result.Value)
}
if result.Extra != "value" {
t.Errorf("extra should be 'value', got: %#v", result.Extra)
}
}
func TestWeakDecode(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"foo": "4",
"bar": "value",
}
var result struct {
Foo int
Bar string
}
if err := WeakDecode(input, &result); err != nil {
t.Fatalf("err: %s", err)
}
if result.Foo != 4 {
t.Fatalf("bad: %#v", result)
}
if result.Bar != "value" {
t.Fatalf("bad: %#v", result)
}
}
func testSliceInput(t *testing.T, input map[string]interface{}, expected *Slice) {
var result Slice
err := Decode(input, &result)
if err != nil {
t.Fatalf("got error: %s", err)
}
if result.Vfoo != expected.Vfoo {
t.Errorf("Vfoo expected '%s', got '%s'", expected.Vfoo, result.Vfoo)
}
if result.Vbar == nil {
t.Fatalf("Vbar a slice, got '%#v'", result.Vbar)
}
if len(result.Vbar) != len(expected.Vbar) {
t.Errorf("Vbar length should be %d, got %d", len(expected.Vbar), len(result.Vbar))
}
for i, v := range result.Vbar {
if v != expected.Vbar[i] {
t.Errorf(
"Vbar[%d] should be '%#v', got '%#v'",
i, expected.Vbar[i], v)
}
}
}

View File

@ -0,0 +1,2 @@
bin/*
pkg/*

202
Godeps/_workspace/src/github.com/racker/perigee/LICENSE generated vendored Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,120 @@
# perigee
Perigee provides a REST client that, while it should be generic enough to use with most any RESTful API, is nonetheless optimized to the needs of the OpenStack APIs.
Perigee grew out of the need to refactor out common API access code from the [gorax](http://github.com/racker/gorax) project.
Several things influenced the name of the project.
Numerous elements of the OpenStack ecosystem are named after astronomical artifacts.
Additionally, perigee occurs when two orbiting bodies are closest to each other.
Perigee seemed appropriate for something aiming to bring OpenStack and other RESTful services closer to the end-user.
**This library is still in the very early stages of development. Unless you want to contribute, it probably isn't what you want**
## Installation and Testing
To install:
```bash
go get github.com/racker/perigee
```
To run unit tests:
```bash
go test github.com/racker/perigee
```
## Contributing
The following guidelines are preliminary, as this project is just starting out.
However, this should serve as a working first-draft.
### Branching
The master branch must always be a valid build.
The `go get` command will not work otherwise.
Therefore, development must occur on a different branch.
When creating a feature branch, do so off the master branch:
```bash
git checkout master
git pull
git checkout -b featureBranch
git checkout -b featureBranch-wip # optional
```
Perform all your editing and testing in the WIP-branch.
Feel free to make as many commits as you see fit.
You may even open "WIP" pull requests from your feature branch to seek feedback.
WIP pull requests will **never** be merged, however.
To get code merged, you'll need to "squash" your changes into one or more clean commits in the feature branch.
These steps should be followed:
```bash
git checkout featureBranch
git merge --squash featureBranch-wip
git commit -a
git push origin featureBranch
```
You may now open a nice, clean, self-contained pull request from featureBranch to master.
The `git commit -a` command above will open a text editor so that
you may provide a comprehensive description of the changes.
In general, when submitting a pull request against master,
be sure to answer the following questions:
- What is the problem?
- Why is it a problem?
- What is your solution?
- How does your solution work? (Recommended for non-trivial changes.)
- Why should we use your solution over someone elses? (Recommended especially if multiple solutions being discussed.)
Remember that monster-sized pull requests are a bear to code-review,
so having helpful commit logs are an absolute must to review changes as quickly as possible.
Finally, (s)he who breaks master is ultimately responsible for fixing master.
### Source Representation
The Go community firmly believes in a consistent representation for all Go source code.
We do too.
Make sure all source code is passed through "go fmt" *before* you create your pull request.
Please note, however, that we fully acknowledge and recognize that we no longer rely upon punch-cards for representing source files.
Therefore, no 80-column limit exists.
However, if a line exceeds 132 columns, you may want to consider splitting the line.
### Unit and Integration Tests
Pull requests that include non-trivial code changes without accompanying unit tests will be flatly rejected.
While we have no way of enforcing this practice,
you can ensure your code is thoroughly tested by always [writing tests first by intention.](http://en.wikipedia.org/wiki/Test-driven_development)
When creating a pull request, if even one test fails, the PR will be rejected.
Make sure all unit tests pass.
Make sure all integration tests pass.
### Documentation
Private functions and methods which are obvious to anyone unfamiliar with gorax needn't be accompanied by documentation.
However, this is a code-smell; if submitting a PR, expect to justify your decision.
Public functions, regardless of how obvious, **must** have accompanying godoc-style documentation.
This is not to suggest you should provide a tome for each function, however.
Sometimes a link to more information is more appropriate, provided the link is stable, reliable, and pertinent.
Changing documentation often results in bizarre diffs in pull requests, due to text often spanning multiple lines.
To work around this, put [one logical thought or sentence on a single line.](http://rhodesmill.org/brandon/2012/one-sentence-per-line/)
While this looks weird in a plain-text editor,
remember that both godoc and HTML viewers will reflow text.
The source code and its comments should be easy to edit with minimal diff pollution.
Let software dedicated to presenting the documentation to human readers deal with its presentation.
## Examples
t.b.d.

269
Godeps/_workspace/src/github.com/racker/perigee/api.go generated vendored Normal file
View File

@ -0,0 +1,269 @@
// vim: ts=8 sw=8 noet ai
package perigee
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
)
// The UnexpectedResponseCodeError structure represents a mismatch in understanding between server and client in terms of response codes.
// Most often, this is due to an actual error condition (e.g., getting a 404 for a resource when you expect a 200).
// However, it needn't always be the case (e.g., getting a 204 (No Content) response back when a 200 is expected).
type UnexpectedResponseCodeError struct {
Url string
Expected []int
Actual int
Body []byte
}
func (err *UnexpectedResponseCodeError) Error() string {
return fmt.Sprintf("Expected HTTP response code %d when accessing URL(%s); got %d instead with the following body:\n%s", err.Expected, err.Url, err.Actual, string(err.Body))
}
// Request issues an HTTP request, marshaling parameters, and unmarshaling results, as configured in the provided Options parameter.
// The Response structure returned, if any, will include accumulated results recovered from the HTTP server.
// See the Response structure for more details.
func Request(method string, url string, opts Options) (*Response, error) {
var body io.Reader
var response Response
client := opts.CustomClient
if client == nil {
client = new(http.Client)
}
contentType := opts.ContentType
body = nil
if opts.ReqBody != nil {
if contentType == "" {
contentType = "application/json"
}
if contentType == "application/json" {
bodyText, err := json.Marshal(opts.ReqBody)
if err != nil {
return nil, err
}
body = strings.NewReader(string(bodyText))
if opts.DumpReqJson {
log.Printf("Making request:\n%#v\n", string(bodyText))
}
} else {
// assume opts.ReqBody implements the correct interface
body = opts.ReqBody.(io.Reader)
}
}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
if contentType != "" {
req.Header.Add("Content-Type", contentType)
}
if opts.ContentLength > 0 {
req.ContentLength = opts.ContentLength
req.Header.Add("Content-Length", string(opts.ContentLength))
}
if opts.MoreHeaders != nil {
for k, v := range opts.MoreHeaders {
req.Header.Add(k, v)
}
}
if accept := req.Header.Get("Accept"); accept == "" {
accept = opts.Accept
if accept == "" {
accept = "application/json"
}
req.Header.Add("Accept", accept)
}
if opts.SetHeaders != nil {
err = opts.SetHeaders(req)
if err != nil {
return &response, err
}
}
httpResponse, err := client.Do(req)
if httpResponse != nil {
response.HttpResponse = *httpResponse
response.StatusCode = httpResponse.StatusCode
}
if err != nil {
return &response, err
}
// This if-statement is legacy code, preserved for backward compatibility.
if opts.StatusCode != nil {
*opts.StatusCode = httpResponse.StatusCode
}
acceptableResponseCodes := opts.OkCodes
if len(acceptableResponseCodes) != 0 {
if not_in(httpResponse.StatusCode, acceptableResponseCodes) {
b, _ := ioutil.ReadAll(httpResponse.Body)
httpResponse.Body.Close()
return &response, &UnexpectedResponseCodeError{
Url: url,
Expected: acceptableResponseCodes,
Actual: httpResponse.StatusCode,
Body: b,
}
}
}
if opts.Results != nil {
defer httpResponse.Body.Close()
jsonResult, err := ioutil.ReadAll(httpResponse.Body)
response.JsonResult = jsonResult
if err != nil {
return &response, err
}
err = json.Unmarshal(jsonResult, opts.Results)
// This if-statement is legacy code, preserved for backward compatibility.
if opts.ResponseJson != nil {
*opts.ResponseJson = jsonResult
}
}
return &response, err
}
// not_in returns false if, and only if, the provided needle is _not_
// in the given set of integers.
func not_in(needle int, haystack []int) bool {
for _, straw := range haystack {
if needle == straw {
return false
}
}
return true
}
// Post makes a POST request against a server using the provided HTTP client.
// The url must be a fully-formed URL string.
// DEPRECATED. Use Request() instead.
func Post(url string, opts Options) error {
r, err := Request("POST", url, opts)
if opts.Response != nil {
*opts.Response = r
}
return err
}
// Get makes a GET request against a server using the provided HTTP client.
// The url must be a fully-formed URL string.
// DEPRECATED. Use Request() instead.
func Get(url string, opts Options) error {
r, err := Request("GET", url, opts)
if opts.Response != nil {
*opts.Response = r
}
return err
}
// Delete makes a DELETE request against a server using the provided HTTP client.
// The url must be a fully-formed URL string.
// DEPRECATED. Use Request() instead.
func Delete(url string, opts Options) error {
r, err := Request("DELETE", url, opts)
if opts.Response != nil {
*opts.Response = r
}
return err
}
// Put makes a PUT request against a server using the provided HTTP client.
// The url must be a fully-formed URL string.
// DEPRECATED. Use Request() instead.
func Put(url string, opts Options) error {
r, err := Request("PUT", url, opts)
if opts.Response != nil {
*opts.Response = r
}
return err
}
// Options describes a set of optional parameters to the various request calls.
//
// The custom client can be used for a variety of purposes beyond selecting encrypted versus unencrypted channels.
// Transports can be defined to provide augmented logging, header manipulation, et. al.
//
// If the ReqBody field is provided, it will be embedded as a JSON object.
// Otherwise, provide nil.
//
// If JSON output is to be expected from the response,
// provide either a pointer to the container structure in Results,
// or a pointer to a nil-initialized pointer variable.
// The latter method will cause the unmarshaller to allocate the container type for you.
// If no response is expected, provide a nil Results value.
//
// The MoreHeaders map, if non-nil or empty, provides a set of headers to add to those
// already present in the request. At present, only Accepted and Content-Type are set
// by default.
//
// OkCodes provides a set of acceptable, positive responses.
//
// If provided, StatusCode specifies a pointer to an integer, which will receive the
// returned HTTP status code, successful or not. DEPRECATED; use the Response.StatusCode field instead for new software.
//
// ResponseJson, if specified, provides a means for returning the raw JSON. This is
// most useful for diagnostics. DEPRECATED; use the Response.JsonResult field instead for new software.
//
// DumpReqJson, if set to true, will cause the request to appear to stdout for debugging purposes.
// This attribute may be removed at any time in the future; DO NOT use this attribute in production software.
//
// Response, if set, provides a way to communicate the complete set of HTTP response, raw JSON, status code, and
// other useful attributes back to the caller. Note that the Request() method returns a Response structure as part
// of its public interface; you don't need to set the Response field here to use this structure. The Response field
// exists primarily for legacy or deprecated functions.
//
// SetHeaders allows the caller to provide code to set any custom headers programmatically. Typically, this
// facility can invoke, e.g., SetBasicAuth() on the request to easily set up authentication.
// Any error generated will terminate the request and will propegate back to the caller.
type Options struct {
CustomClient *http.Client
ReqBody interface{}
Results interface{}
MoreHeaders map[string]string
OkCodes []int
StatusCode *int `DEPRECATED`
DumpReqJson bool `UNSUPPORTED`
ResponseJson *[]byte `DEPRECATED`
Response **Response
ContentType string `json:"Content-Type,omitempty"`
ContentLength int64 `json:"Content-Length,omitempty"`
Accept string `json:"Accept,omitempty"`
SetHeaders func(r *http.Request) error
}
// Response contains return values from the various request calls.
//
// HttpResponse will return the http response from the request call.
// Note: HttpResponse.Body is always closed and will not be available from this return value.
//
// StatusCode specifies the returned HTTP status code, successful or not.
//
// If Results is specified in the Options:
// - JsonResult will contain the raw return from the request call
// This is most useful for diagnostics.
// - Result will contain the unmarshalled json either in the Result passed in
// or the unmarshaller will allocate the container type for you.
type Response struct {
HttpResponse http.Response
JsonResult []byte
Results interface{}
StatusCode int
}

View File

@ -0,0 +1,226 @@
package perigee
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestNormal(t *testing.T) {
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("testing"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
response, err := Request("GET", ts.URL, Options{})
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if response.StatusCode != 200 {
t.Fatalf("response code %d is not 200", response.StatusCode)
}
}
func TestOKCodes(t *testing.T) {
expectCode := 201
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(expectCode)
w.Write([]byte("testing"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
options := Options{
OkCodes: []int{expectCode},
}
results, err := Request("GET", ts.URL, options)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if results.StatusCode != expectCode {
t.Fatalf("response code %d is not %d", results.StatusCode, expectCode)
}
}
func TestLocation(t *testing.T) {
newLocation := "http://www.example.com"
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", newLocation)
w.Write([]byte("testing"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
response, err := Request("GET", ts.URL, Options{})
if err != nil {
t.Fatalf("should not have error: %s", err)
}
location, err := response.HttpResponse.Location()
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if location.String() != newLocation {
t.Fatalf("location returned \"%s\" is not \"%s\"", location.String(), newLocation)
}
}
func TestHeaders(t *testing.T) {
newLocation := "http://www.example.com"
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", newLocation)
w.Write([]byte("testing"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
response, err := Request("GET", ts.URL, Options{})
if err != nil {
t.Fatalf("should not have error: %s", err)
}
location := response.HttpResponse.Header.Get("Location")
if location == "" {
t.Fatalf("Location should not empty")
}
if location != newLocation {
t.Fatalf("location returned \"%s\" is not \"%s\"", location, newLocation)
}
}
func TestCustomHeaders(t *testing.T) {
var contentType, accept, contentLength string
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m := map[string][]string(r.Header)
contentType = m["Content-Type"][0]
accept = m["Accept"][0]
contentLength = m["Content-Length"][0]
})
ts := httptest.NewServer(handler)
defer ts.Close()
_, err := Request("GET", ts.URL, Options{
ContentLength: 5,
ContentType: "x-application/vb",
Accept: "x-application/c",
ReqBody: strings.NewReader("Hello"),
})
if err != nil {
t.Fatalf(err.Error())
}
if contentType != "x-application/vb" {
t.Fatalf("I expected x-application/vb; got ", contentType)
}
if contentLength != "5" {
t.Fatalf("I expected 5 byte content length; got ", contentLength)
}
if accept != "x-application/c" {
t.Fatalf("I expected x-application/c; got ", accept)
}
}
func TestJson(t *testing.T) {
newLocation := "http://www.example.com"
jsonBytes := []byte(`{"foo": {"bar": "baz"}}`)
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", newLocation)
w.Write(jsonBytes)
})
ts := httptest.NewServer(handler)
defer ts.Close()
type Data struct {
Foo struct {
Bar string `json:"bar"`
} `json:"foo"`
}
var data Data
response, err := Request("GET", ts.URL, Options{Results: &data})
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if bytes.Compare(jsonBytes, response.JsonResult) != 0 {
t.Fatalf("json returned \"%s\" is not \"%s\"", response.JsonResult, jsonBytes)
}
if data.Foo.Bar != "baz" {
t.Fatalf("Results returned %v", data)
}
}
func TestSetHeaders(t *testing.T) {
var wasCalled bool
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hi"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
_, err := Request("GET", ts.URL, Options{
SetHeaders: func(r *http.Request) error {
wasCalled = true
return nil
},
})
if err != nil {
t.Fatal(err)
}
if !wasCalled {
t.Fatal("I expected header setter callback to be called, but it wasn't")
}
myError := fmt.Errorf("boo")
_, err = Request("GET", ts.URL, Options{
SetHeaders: func(r *http.Request) error {
return myError
},
})
if err != myError {
t.Fatal("I expected errors to propegate back to the caller.")
}
}
func TestBodilessMethodsAreSentWithoutContentHeaders(t *testing.T) {
var h map[string][]string
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h = r.Header
})
ts := httptest.NewServer(handler)
defer ts.Close()
_, err := Request("GET", ts.URL, Options{})
if err != nil {
t.Fatalf(err.Error())
}
if len(h["Content-Type"]) != 0 {
t.Fatalf("I expected nothing for Content-Type but got ", h["Content-Type"])
}
if len(h["Content-Length"]) != 0 {
t.Fatalf("I expected nothing for Content-Length but got ", h["Content-Length"])
}
}

View File

@ -0,0 +1,14 @@
language: go
install:
- go get -v -tags 'fixtures acceptance' ./...
go:
- 1.1
- 1.2
- tip
script: script/cibuild
after_success:
- go get code.google.com/p/go.tools/cmd/cover
- go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls
- export PATH=$PATH:$HOME/gopath/bin/
- goveralls 2k7PTU3xa474Hymwgdj6XjqenNfGTNkO8

View File

@ -0,0 +1,275 @@
# Contributing to gophercloud
- [Getting started](#getting-started)
- [Tests](#tests)
- [Style guide](#basic-style-guide)
- [5 ways to get involved](#5-ways-to-get-involved)
## Setting up your git workspace
As a contributor you will need to setup your workspace in a slightly different
way than just downloading it. Here are the basic installation instructions:
1. Configure your `$GOPATH` and run `go get` as described in the main
[README](/README.md#how-to-install).
2. Move into the directory that houses your local repository:
```bash
cd ${GOPATH}/src/github.com/rackspace/gophercloud
```
3. Fork the `rackspace/gophercloud` repository and update your remote refs. You
will need to rename the `origin` remote branch to `upstream`, and add your
fork as `origin` instead:
```bash
git remote rename origin upstream
git remote add origin git@github.com/<my_username>/gophercloud
```
4. Checkout the latest development branch ([click here](/branches) to see all
the branches):
```bash
git checkout release/v1.0.1
```
5. If you're working on something (discussed more in detail below), you will
need to checkout a new feature branch:
```bash
git checkout -b my-new-feature
```
Another thing to bear in mind is that you will need to add a few extra
environment variables for acceptance tests - this is documented in our
[acceptance tests readme](/acceptance).
## Tests
When working on a new or existing feature, testing will be the backbone of your
work since it helps uncover and prevent regressions in the codebase. There are
two types of test we use in gophercloud: unit tests and acceptance tests, which
are both described below.
### Unit tests
Unit tests are the fine-grained tests that establish and ensure the behaviour
of individual units of functionality. We usually test on an
operation-by-operation basis (an operation typically being an API action) with
the use of mocking to set up explicit expectations. Each operation will set up
its HTTP response expectation, and then test how the system responds when fed
this controlled, pre-determined input.
To make life easier, we've introduced a bunch of test helpers to simplify the
process of testing expectations with assertions:
```go
import (
"testing"
"github.com/rackspace/gophercloud/testhelper"
)
func TestSomething(t *testing.T) {
result, err := Operation()
testhelper.AssertEquals(t, "foo", result.Bar)
testhelper.AssertNoErr(t, err)
}
func TestSomethingElse(t *testing.T) {
testhelper.CheckEquals(t, "expected", "actual")
}
```
`AssertEquals` and `AssertNoErr` will throw a fatal error if a value does not
match an expected value or if an error has been declared, respectively. You can
also use `CheckEquals` and `CheckNoErr` for the same purpose; the only difference
being that `t.Errorf` is raised rather than `t.Fatalf`.
Here is a truncated example of mocked HTTP responses:
```go
import (
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
func TestGet(t *testing.T) {
// Setup the HTTP request multiplexer and server
th.SetupHTTP()
defer th.TeardownHTTP()
th.Mux.HandleFunc("/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
// Test we're using the correct HTTP method
th.TestMethod(t, r, "GET")
// Test we're setting the auth token
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
// Set the appropriate headers for our mocked response
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
// Set the HTTP body
fmt.Fprintf(w, `
{
"network": {
"status": "ACTIVE",
"name": "private-network",
"admin_state_up": true,
"tenant_id": "4fd44f30292945e481c7b8a0c8908869",
"shared": true,
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
}
}
`)
})
// Call our API operation
network, err := Get(fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract()
// Assert no errors and equality
th.AssertNoErr(t, err)
th.AssertEquals(t, n.Status, "ACTIVE")
}
```
### Acceptance tests
As we've already mentioned, unit tests have a very narrow and confined focus -
they test small units of behaviour. Acceptance tests on the other hand have a
far larger scope: they are fully functional tests that test the entire API of a
service in one fell swoop. They don't care about unit isolation or mocking
expectations, they instead do a full run-through and consequently test how the
entire system _integrates_ together. When an API satisfies expectations, it
proves by default that the requirements for a contract have been met.
Please be aware that acceptance tests will hit a live API - and may incur
service charges from your provider. Although most tests handle their own
teardown procedures, it is always worth manually checking that resources are
deleted after the test suite finishes.
### Running tests
To run all tests:
```bash
go test ./...
```
To run all tests with verbose output:
```bash
go test -v ./...
```
To run tests that match certain [build tags]():
```bash
go test -tags "foo bar" ./...
```
To run tests for a particular sub-package:
```bash
cd ./path/to/package && go test .
```
## Basic style guide
We follow the standard formatting recommendations and language idioms set out
in the [Effective Go](https://golang.org/doc/effective_go.html) guide. It's
definitely worth reading - but the relevant sections are
[formatting](https://golang.org/doc/effective_go.html#formatting)
and [names](https://golang.org/doc/effective_go.html#names).
## 5 ways to get involved
There are five main ways you can get involved in our open-source project, and
each is described briefly below. Once you've made up your mind and decided on
your fix, you will need to follow the same basic steps that all submissions are
required to adhere to:
1. [fork](https://help.github.com/articles/fork-a-repo/) the `rackspace/gophercloud` repository
2. checkout a [new branch](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches)
3. submit your branch as a [pull request](https://help.github.com/articles/creating-a-pull-request/)
### 1. Providing feedback
On of the easiest ways to get readily involved in our project is to let us know
about your experiences using our SDK. Feedback like this is incredibly useful
to us, because it allows us to refine and change features based on what our
users want and expect of us. There are a bunch of ways to get in contact! You
can [ping us](mailto:sdk-support@rackspace.com) via e-mail, talk to us on irc
(#rackspace-dev on freenode), [tweet us](https://twitter.com/rackspace), or
submit an issue on our [bug tracker](/issues). Things you might like to tell us
are:
* how easy was it to start using our SDK?
* did it meet your expectations? If not, why not?
* did our documentation help or hinder you?
* what could we improve in general?
### 2. Fixing bugs
If you want to start fixing open bugs, we'd really appreciate that! Bug fixing
is central to any project. The best way to get started is by heading to our
[bug tracker](https://github.com/rackspace/gophercloud/issues) and finding open
bugs that you think nobody is working on. It might be useful to comment on the
thread to see the current state of the issue and if anybody has made any
breakthroughs on it so far.
### 3. Improving documentation
We have three forms of documentation:
* short README documents that briefly introduce a topic
* reference documentation on [godoc.org](http://godoc.org) that is automatically
generated from source code comments
* user documentation on our [homepage](http://gophercloud.io) that includes
getting started guides, installation guides and code samples
If you feel that a certain section could be improved - whether it's to clarify
ambiguity, correct a technical mistake, or to fix a grammatical error - please
feel entitled to do so! We welcome doc pull requests with the same childlike
enthusiasm as any other contribution!
### 4. Optimizing existing features
If you would like to improve or optimize an existing feature, please be aware
that we adhere to [semantic versioning](http://semver.org) - which means that
we cannot introduce breaking changes to the API without a major version change
(v1.x -> v2.x). Making that leap is a big step, so we encourage contributors to
refactor rather than rewrite. Running tests will prevent regression and avoid
the possibility of breaking somebody's current implementation.
Another tip is to keep the focus of your work as small as possible - try not to
introduce a change that affects lots and lots of files because it introduces
added risk and increases the cognitive load on the reviewers checking your
work. Change-sets which are easily understood and will not negatively impact
users are more likely to be integrated quickly.
Lastly, if you're seeking to optimize a particular operation, you should try to
demonstrate a negative performance impact - perhaps using go's inbuilt
[benchmark capabilities](http://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go).
### 5. Working on a new feature
If you've found something we've left out, definitely feel free to start work on
introducing that feature. It's always useful to open an issue or submit a pull
request early on to indicate your intent to a core contributor - this enables
quick/early feedback and can help steer you in the right direction by avoiding
known issues. It might also help you avoid losing time implementing something
that might not ever work. One tip is to prefix your Pull Request issue title
with [wip] - then people know it's a work in progress.
You must ensure that all of your work is well tested - both in terms of unit
and acceptance tests. Untested code will not be merged because it introduces
too much of a risk to end-users.
Happy hacking!

View File

@ -0,0 +1,12 @@
Contributors
============
| Name | Email |
| ---- | ----- |
| Samuel A. Falvo II | <sam.falvo@rackspace.com>
| Glen Campbell | <glen.campbell@rackspace.com>
| Jesse Noller | <jesse.noller@rackspace.com>
| Jon Perritt | <jon.perritt@rackspace.com>
| Ash Wilson | <ash.wilson@rackspace.com>
| Jamie Hannaford | <jamie.hannaford@rackspace.com>
| Don Schenck | don.schenck@rackspace.com>

View File

@ -0,0 +1,5 @@
{
"ImportPath": "github.com/rackspace/gophercloud",
"GoVersion": "go1.3.3",
"Deps": []
}

View File

@ -0,0 +1,5 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

View File

@ -0,0 +1,191 @@
Copyright 2012-2013 Rackspace, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -0,0 +1,161 @@
# Gophercloud: the OpenStack SDK for Go
[![Build Status](https://travis-ci.org/rackspace/gophercloud.svg?branch=master)](https://travis-ci.org/rackspace/gophercloud)
Gophercloud is a flexible SDK that allows you to consume and work with OpenStack
clouds in a simple and idiomatic way using golang. Many services are supported,
including Compute, Block Storage, Object Storage, Networking, and Identity.
Each service API is backed with getting started guides, code samples, reference
documentation, unit tests and acceptance tests.
## Useful links
* [Gophercloud homepage](http://gophercloud.io)
* [Reference documentation](http://godoc.org/github.com/rackspace/gophercloud)
* [Getting started guides](http://gophercloud.io/docs)
* [Effective Go](https://golang.org/doc/effective_go.html)
## How to install
Before installing, you need to ensure that your [GOPATH environment variable](https://golang.org/doc/code.html#GOPATH)
is pointing to an appropriate directory where you want to install Gophercloud:
```bash
mkdir $HOME/go
export GOPATH=$HOME/go
```
To protect yourself against changes in your dependencies, we highly recommend choosing a
[dependency management solution](https://code.google.com/p/go-wiki/wiki/PackageManagementTools) for
your projects, such as [godep](https://github.com/tools/godep). Once this is set up, you can install
Gophercloud as a dependency like so:
```bash
go get github.com/rackspace/gophercloud
# Edit your code to import relevant packages from "github.com/rackspace/gophercloud"
godep save ./...
```
This will install all the source files you need into a `Godeps/_workspace` directory, which is
referenceable from your own source files when you use the `godep go` command.
## Getting started
### Credentials
Because you'll be hitting an API, you will need to retrieve your OpenStack
credentials and either store them as environment variables or in your local Go
files. The first method is recommended because it decouples credential
information from source code, allowing you to push the latter to your version
control system without any security risk.
You will need to retrieve the following:
* username
* password
* tenant name or tenant ID
* a valid Keystone identity URL
For users that have the OpenStack dashboard installed, there's a shortcut. If
you visit the `project/access_and_security` path in Horizon and click on the
"Download OpenStack RC File" button at the top right hand corner, you will
download a bash file that exports all of your access details to environment
variables. To execute the file, run `source admin-openrc.sh` and you will be
prompted for your password.
### Authentication
Once you have access to your credentials, you can begin plugging them into
Gophercloud. The next step is authentication, and this is handled by a base
"Provider" struct. To get one, you can either pass in your credentials
explicitly, or tell Gophercloud to use environment variables:
```go
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
"github.com/rackspace/gophercloud/openstack/utils"
)
// Option 1: Pass in the values yourself
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://my-openstack.com:5000/v2.0",
Username: "{username}",
Password: "{password}",
TenantID: "{tenant_id}",
}
// Option 2: Use a utility function to retrieve all your environment variables
opts, err := openstack.AuthOptionsFromEnv()
```
Once you have the `opts` variable, you can pass it in and get back a
`ProviderClient` struct:
```go
provider, err := openstack.AuthenticatedClient(opts)
```
The `ProviderClient` is the top-level client that all of your OpenStack services
derive from. The provider contains all of the authentication details that allow
your Go code to access the API - such as the base URL and token ID.
### Provision a server
Once we have a base Provider, we inject it as a dependency into each OpenStack
service. In order to work with the Compute API, we need a Compute service
client; which can be created like so:
```go
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
```
We then use this `client` for any Compute API operation we want. In our case,
we want to provision a new server - so we invoke the `Create` method and pass
in the flavor ID (hardware specification) and image ID (operating system) we're
interested in:
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
server, err := servers.Create(client, servers.CreateOpts{
Name: "My new server!",
FlavorRef: "flavor_id",
ImageRef: "image_id",
}).Extract()
```
If you are unsure about what images and flavors are, you can read our [Compute
Getting Started guide](http://gophercloud.io/docs/compute). The above code
sample creates a new server with the parameters, and embodies the new resource
in the `server` variable (a
[`servers.Server`](http://godoc.org/github.com/rackspace/gophercloud) struct).
### Next steps
Cool! You've handled authentication, got your `ProviderClient` and provisioned
a new server. You're now ready to use more OpenStack services.
* [Getting started with Compute](http://gophercloud.io/docs/compute)
* [Getting started with Object Storage](http://gophercloud.io/docs/object-storage)
* [Getting started with Networking](http://gophercloud.io/docs/networking)
* [Getting started with Block Storage](http://gophercloud.io/docs/block-storage)
* [Getting started with Identity](http://gophercloud.io/docs/identity)
## Contributing
Engaging the community and lowering barriers for contributors is something we
care a lot about. For this reason, we've taken the time to write a [contributing
guide](./CONTRIBUTING.md) for folks interested in getting involved in our project.
If you're not sure how you can get involved, feel free to submit an issue or
[e-mail us](mailto:sdk-support@rackspace.com) privately. You don't need to be a
Go expert - all members of the community are welcome!
## Help and feedback
If you're struggling with something or have spotted a potential bug, feel free
to submit an issue to our [bug tracker](/issues) or e-mail us directly at
[sdk-support@rackspace.com](mailto:sdk-support@rackspace.com).

View File

@ -0,0 +1,338 @@
# Upgrading to v1.0.0
With the arrival of this new major version increment, the unfortunate news is
that breaking changes have been introduced to existing services. The API
has been completely rewritten from the ground up to make the library more
extensible, maintainable and easy-to-use.
Below we've compiled upgrade instructions for the various services that
existed before. If you have a specific issue that is not addressed below,
please [submit an issue](/issues/new) or
[e-mail our support team](mailto:sdk-support@rackspace.com).
* [Authentication](#authentication)
* [Servers](#servers)
* [List servers](#list-servers)
* [Get server details](#get-server-details)
* [Create server](#create-server)
* [Resize server](#resize-server)
* [Reboot server](#reboot-server)
* [Update server](#update-server)
* [Rebuild server](#rebuild-server)
* [Change admin password](#change-admin-password)
* [Delete server](#delete-server)
* [Rescue server](#rescue-server)
* [Images and flavors](#images-and-flavors)
* [List images](#list-images)
* [List flavors](#list-flavors)
* [Create/delete image](#createdelete-image)
* [Other](#other)
* [List keypairs](#list-keypairs)
* [Create/delete keypair](#createdelete-keypair)
* [List IP addresses](#list-ip-addresses)
# Authentication
One of the major differences that this release introduces is the level of
sub-packaging to differentiate between services and providers. You now have
the option of authenticating with OpenStack and other providers (like Rackspace).
To authenticate with a vanilla OpenStack installation, you can either specify
your credentials like this:
```go
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
)
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://my-openstack.com:5000/v2.0",
Username: "{username}",
Password: "{password}",
TenantID: "{tenant_id}",
}
```
Or have them pulled in through environment variables, like this:
```go
opts, err := openstack.AuthOptionsFromEnv()
```
Once you have your `AuthOptions` struct, you pass it in to get back a `Provider`,
like so:
```go
provider, err := openstack.AuthenticatedClient(opts)
```
This provider is the top-level structure that all services are created from.
# Servers
Before you can interact with the Compute API, you need to retrieve a
`gophercloud.ServiceClient`. To do this:
```go
// Define your region, etc.
opts := gophercloud.EndpointOpts{Region: "RegionOne"}
client, err := openstack.NewComputeV2(provider, opts)
```
## List servers
All operations that involve API collections (servers, flavors, images) now use
the `pagination.Pager` interface. This interface represents paginated entities
that can be iterated over.
Once you have a Pager, you can then pass a callback function into its `EachPage`
method, and this will allow you to traverse over the collection and execute
arbitrary functionality. So, an example with list servers:
```go
import (
"fmt"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
)
// We have the option of filtering the server list. If we want the full
// collection, leave it as an empty struct or nil
opts := servers.ListOpts{Name: "server_1"}
// Retrieve a pager (i.e. a paginated collection)
pager := servers.List(client, opts)
// Define an anonymous function to be executed on each page's iteration
err := pager.EachPage(func(page pagination.Page) (bool, error) {
serverList, err := servers.ExtractServers(page)
// `s' will be a servers.Server struct
for _, s := range serverList {
fmt.Printf("We have a server. ID=%s, Name=%s", s.ID, s.Name)
}
})
```
## Get server details
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
// Get the HTTP result
response := servers.Get(client, "server_id")
// Extract a Server struct from the response
server, err := response.Extract()
```
## Create server
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
// Define our options
opts := servers.CreateOpts{
Name: "new_server",
FlavorRef: "flavorID",
ImageRef: "imageID",
}
// Get our response
response := servers.Create(client, opts)
// Extract
server, err := response.Extract()
```
## Change admin password
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
result := servers.ChangeAdminPassword(client, "server_id", "newPassword_&123")
```
## Resize server
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
result := servers.Resize(client, "server_id", "new_flavor_id")
```
## Reboot server
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
// You have a choice of two reboot methods: servers.SoftReboot or servers.HardReboot
result := servers.Reboot(client, "server_id", servers.SoftReboot)
```
## Update server
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
opts := servers.UpdateOpts{Name: "new_name"}
server, err := servers.Update(client, "server_id", opts).Extract()
```
## Rebuild server
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
// You have the option of specifying additional options
opts := RebuildOpts{
Name: "new_name",
AdminPass: "admin_password",
ImageID: "image_id",
Metadata: map[string]string{"owner": "me"},
}
result := servers.Rebuild(client, "server_id", opts)
// You can extract a servers.Server struct from the HTTP response
server, err := result.Extract()
```
## Delete server
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
response := servers.Delete(client, "server_id")
```
## Rescue server
The server rescue extension for Compute is not currently supported.
# Images and flavors
## List images
As with listing servers (see above), you first retrieve a Pager, and then pass
in a callback over each page:
```go
import (
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/openstack/compute/v2/images"
)
// We have the option of filtering the image list. If we want the full
// collection, leave it as an empty struct
opts := images.ListOpts{ChangesSince: "2014-01-01T01:02:03Z", Name: "Ubuntu 12.04"}
// Retrieve a pager (i.e. a paginated collection)
pager := images.List(client, opts)
// Define an anonymous function to be executed on each page's iteration
err := pager.EachPage(func(page pagination.Page) (bool, error) {
imageList, err := images.ExtractImages(page)
for _, i := range imageList {
// "i" will be an images.Image
}
})
```
## List flavors
```go
import (
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
)
// We have the option of filtering the flavor list. If we want the full
// collection, leave it as an empty struct
opts := flavors.ListOpts{ChangesSince: "2014-01-01T01:02:03Z", MinRAM: 4}
// Retrieve a pager (i.e. a paginated collection)
pager := flavors.List(client, opts)
// Define an anonymous function to be executed on each page's iteration
err := pager.EachPage(func(page pagination.Page) (bool, error) {
flavorList, err := networks.ExtractFlavors(page)
for _, f := range flavorList {
// "f" will be a flavors.Flavor
}
})
```
## Create/delete image
Image management has been shifted to Glance, but unfortunately this service is
not supported as of yet. You can, however, list Compute images like so:
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/images"
// Retrieve a pager (i.e. a paginated collection)
pager := images.List(client, opts)
// Define an anonymous function to be executed on each page's iteration
err := pager.EachPage(func(page pagination.Page) (bool, error) {
imageList, err := images.ExtractImages(page)
for _, i := range imageList {
// "i" will be an images.Image
}
})
```
# Other
## List keypairs
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
// Retrieve a pager (i.e. a paginated collection)
pager := keypairs.List(client, opts)
// Define an anonymous function to be executed on each page's iteration
err := pager.EachPage(func(page pagination.Page) (bool, error) {
keyList, err := keypairs.ExtractKeyPairs(page)
for _, k := range keyList {
// "k" will be a keypairs.KeyPair
}
})
```
## Create/delete keypairs
To create a new keypair, you need to specify its name and, optionally, a
pregenerated OpenSSH-formatted public key.
```go
import "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
opts := keypairs.CreateOpts{
Name: "new_key",
PublicKey: "...",
}
response := keypairs.Create(client, opts)
key, err := response.Extract()
```
To delete an existing keypair:
```go
response := keypairs.Delete(client, "keypair_id")
```
## List IP addresses
This operation is not currently supported.

View File

@ -0,0 +1,57 @@
# Gophercloud Acceptance tests
The purpose of these acceptance tests is to validate that SDK features meet
the requirements of a contract - to consumers, other parts of the library, and
to a remote API.
> **Note:** Because every test will be run against a real API endpoint, you
> may incur bandwidth and service charges for all the resource usage. These
> tests *should* remove their remote products automatically. However, there may
> be certain cases where this does not happen; always double-check to make sure
> you have no stragglers left behind.
### Step 1. Set environment variables
A lot of tests rely on environment variables for configuration - so you will need
to set them before running the suite. If you're testing against pure OpenStack APIs,
you can download a file that contains all of these variables for you: just visit
the `project/access_and_security` page in your control panel and click the "Download
OpenStack RC File" button at the top right. For all other providers, you will need
to set them manually.
#### Authentication
|Name|Description|
|---|---|
|`OS_USERNAME`|Your API username|
|`OS_PASSWORD`|Your API password|
|`OS_AUTH_URL`|The identity URL you need to authenticate|
|`OS_TENANT_NAME`|Your API tenant name|
|`OS_TENANT_ID`|Your API tenant ID|
|`RS_USERNAME`|Your Rackspace username|
|`RS_API_KEY`|Your Rackspace API key|
#### General
|Name|Description|
|---|---|
|`OS_REGION_NAME`|The region you want your resources to reside in|
|`RS_REGION`|Rackspace region you want your resource to reside in|
#### Compute
|Name|Description|
|---|---|
|`OS_IMAGE_ID`|The ID of the image your want your server to be based on|
|`OS_FLAVOR_ID`|The ID of the flavor you want your server to be based on|
|`OS_FLAVOR_ID_RESIZE`|The ID of the flavor you want your server to be resized to|
|`RS_IMAGE_ID`|The ID of the image you want servers to be created with|
|`RS_FLAVOR_ID`|The ID of the flavor you want your server to be created with|
### 2. Run the test suite
From the root directory, run:
```
./script/acceptancetest
```

View File

@ -0,0 +1,70 @@
// +build acceptance
package v1
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/snapshots"
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestSnapshots(t *testing.T) {
client, err := newClient(t)
th.AssertNoErr(t, err)
v, err := volumes.Create(client, &volumes.CreateOpts{
Name: "gophercloud-test-volume",
Size: 1,
}).Extract()
th.AssertNoErr(t, err)
err = volumes.WaitForStatus(client, v.ID, "available", 120)
th.AssertNoErr(t, err)
t.Logf("Created volume: %v\n", v)
ss, err := snapshots.Create(client, &snapshots.CreateOpts{
Name: "gophercloud-test-snapshot",
VolumeID: v.ID,
}).Extract()
th.AssertNoErr(t, err)
err = snapshots.WaitForStatus(client, ss.ID, "available", 120)
th.AssertNoErr(t, err)
t.Logf("Created snapshot: %+v\n", ss)
err = snapshots.Delete(client, ss.ID).ExtractErr()
th.AssertNoErr(t, err)
err = gophercloud.WaitFor(120, func() (bool, error) {
_, err := snapshots.Get(client, ss.ID).Extract()
if err != nil {
return true, nil
}
return false, nil
})
th.AssertNoErr(t, err)
t.Log("Deleted snapshot\n")
err = volumes.Delete(client, v.ID).ExtractErr()
th.AssertNoErr(t, err)
err = gophercloud.WaitFor(120, func() (bool, error) {
_, err := volumes.Get(client, v.ID).Extract()
if err != nil {
return true, nil
}
return false, nil
})
th.AssertNoErr(t, err)
t.Log("Deleted volume\n")
}

View File

@ -0,0 +1,63 @@
// +build acceptance blockstorage
package v1
import (
"os"
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func newClient(t *testing.T) (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
th.AssertNoErr(t, err)
client, err := openstack.AuthenticatedClient(ao)
th.AssertNoErr(t, err)
return openstack.NewBlockStorageV1(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}
func TestVolumes(t *testing.T) {
client, err := newClient(t)
th.AssertNoErr(t, err)
cv, err := volumes.Create(client, &volumes.CreateOpts{
Size: 1,
Name: "gophercloud-test-volume",
}).Extract()
th.AssertNoErr(t, err)
defer func() {
err = volumes.WaitForStatus(client, cv.ID, "available", 60)
th.AssertNoErr(t, err)
err = volumes.Delete(client, cv.ID).ExtractErr()
th.AssertNoErr(t, err)
}()
_, err = volumes.Update(client, cv.ID, &volumes.UpdateOpts{
Name: "gophercloud-updated-volume",
}).Extract()
th.AssertNoErr(t, err)
v, err := volumes.Get(client, cv.ID).Extract()
th.AssertNoErr(t, err)
t.Logf("Got volume: %+v\n", v)
if v.Name != "gophercloud-updated-volume" {
t.Errorf("Unable to update volume: Expected name: gophercloud-updated-volume\nActual name: %s", v.Name)
}
err = volumes.List(client, &volumes.ListOpts{Name: "gophercloud-updated-volume"}).EachPage(func(page pagination.Page) (bool, error) {
vols, err := volumes.ExtractVolumes(page)
th.CheckEquals(t, 1, len(vols))
return true, err
})
th.AssertNoErr(t, err)
}

View File

@ -0,0 +1,49 @@
// +build acceptance
package v1
import (
"testing"
"time"
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumetypes"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestVolumeTypes(t *testing.T) {
client, err := newClient(t)
th.AssertNoErr(t, err)
vt, err := volumetypes.Create(client, &volumetypes.CreateOpts{
ExtraSpecs: map[string]interface{}{
"capabilities": "gpu",
"priority": 3,
},
Name: "gophercloud-test-volumeType",
}).Extract()
th.AssertNoErr(t, err)
defer func() {
time.Sleep(10000 * time.Millisecond)
err = volumetypes.Delete(client, vt.ID).ExtractErr()
if err != nil {
t.Error(err)
return
}
}()
t.Logf("Created volume type: %+v\n", vt)
vt, err = volumetypes.Get(client, vt.ID).Extract()
th.AssertNoErr(t, err)
t.Logf("Got volume type: %+v\n", vt)
err = volumetypes.List(client).EachPage(func(page pagination.Page) (bool, error) {
volTypes, err := volumetypes.ExtractVolumeTypes(page)
if len(volTypes) != 1 {
t.Errorf("Expected 1 volume type, got %d", len(volTypes))
}
t.Logf("Listing volume types: %+v\n", volTypes)
return true, err
})
th.AssertNoErr(t, err)
}

View File

@ -0,0 +1,40 @@
// +build acceptance
package openstack
import (
"os"
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
)
func TestAuthenticatedClient(t *testing.T) {
// Obtain credentials from the environment.
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
t.Fatalf("Unable to acquire credentials: %v", err)
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
t.Fatalf("Unable to authenticate: %v", err)
}
if client.TokenID == "" {
t.Errorf("No token ID assigned to the client")
}
t.Logf("Client successfully acquired a token: %v", client.TokenID)
// Find the storage service in the service catalog.
storage, err := openstack.NewObjectStorageV1(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
if err != nil {
t.Errorf("Unable to locate a storage service: %v", err)
} else {
t.Logf("Located a storage service at endpoint: [%s]", storage.Endpoint)
}
}

View File

@ -0,0 +1,55 @@
// +build acceptance
package v2
import (
"testing"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestBootFromVolume(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
choices, err := ComputeChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
name := tools.RandomString("Gophercloud-", 8)
t.Logf("Creating server [%s].", name)
bd := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
UUID: choices.ImageID,
SourceType: bootfromvolume.Image,
VolumeSize: 10,
},
}
serverCreateOpts := servers.CreateOpts{
Name: name,
FlavorRef: choices.FlavorID,
ImageRef: choices.ImageID,
}
server, err := bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
serverCreateOpts,
bd,
}).Extract()
th.AssertNoErr(t, err)
if err = waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
t.Logf("Created server: %+v\n", server)
defer servers.Delete(client, server.ID)
t.Logf("Deleting server [%s]...", name)
}

View File

@ -0,0 +1,97 @@
// +build acceptance common
package v2
import (
"fmt"
"os"
"strings"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/openstack"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
)
func newClient() (*gophercloud.ServiceClient, error) {
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
return openstack.NewComputeV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
}
func waitForStatus(client *gophercloud.ServiceClient, server *servers.Server, status string) error {
return tools.WaitFor(func() (bool, error) {
latest, err := servers.Get(client, server.ID).Extract()
if err != nil {
return false, err
}
if latest.Status == status {
// Success!
return true, nil
}
return false, nil
})
}
// ComputeChoices contains image and flavor selections for use by the acceptance tests.
type ComputeChoices struct {
// ImageID contains the ID of a valid image.
ImageID string
// FlavorID contains the ID of a valid flavor.
FlavorID string
// FlavorIDResize contains the ID of a different flavor available on the same OpenStack installation, that is distinct
// from FlavorID.
FlavorIDResize string
}
// ComputeChoicesFromEnv populates a ComputeChoices struct from environment variables.
// If any required state is missing, an `error` will be returned that enumerates the missing properties.
func ComputeChoicesFromEnv() (*ComputeChoices, error) {
imageID := os.Getenv("OS_IMAGE_ID")
flavorID := os.Getenv("OS_FLAVOR_ID")
flavorIDResize := os.Getenv("OS_FLAVOR_ID_RESIZE")
missing := make([]string, 0, 3)
if imageID == "" {
missing = append(missing, "OS_IMAGE_ID")
}
if flavorID == "" {
missing = append(missing, "OS_FLAVOR_ID")
}
if flavorIDResize == "" {
missing = append(missing, "OS_FLAVOR_ID_RESIZE")
}
notDistinct := ""
if flavorID == flavorIDResize {
notDistinct = "OS_FLAVOR_ID and OS_FLAVOR_ID_RESIZE must be distinct."
}
if len(missing) > 0 || notDistinct != "" {
text := "You're missing some important setup:\n"
if len(missing) > 0 {
text += " * These environment variables must be provided: " + strings.Join(missing, ", ") + "\n"
}
if notDistinct != "" {
text += " * " + notDistinct + "\n"
}
return nil, fmt.Errorf(text)
}
return &ComputeChoices{ImageID: imageID, FlavorID: flavorID, FlavorIDResize: flavorIDResize}, nil
}

View File

@ -0,0 +1,47 @@
// +build acceptance compute extensionss
package v2
import (
"testing"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestListExtensions(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
err = extensions.List(client).EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page ---")
exts, err := extensions.ExtractExtensions(page)
th.AssertNoErr(t, err)
for i, ext := range exts {
t.Logf("[%02d] name=[%s]\n", i, ext.Name)
t.Logf(" alias=[%s]\n", ext.Alias)
t.Logf(" description=[%s]\n", ext.Description)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func TestGetExtension(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
ext, err := extensions.Get(client, "os-admin-actions").Extract()
th.AssertNoErr(t, err)
t.Logf("Extension details:")
t.Logf(" name=[%s]\n", ext.Name)
t.Logf(" namespace=[%s]\n", ext.Namespace)
t.Logf(" alias=[%s]\n", ext.Alias)
t.Logf(" description=[%s]\n", ext.Description)
t.Logf(" updated=[%s]\n", ext.Updated)
}

View File

@ -0,0 +1,57 @@
// +build acceptance compute flavors
package v2
import (
"testing"
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
"github.com/rackspace/gophercloud/pagination"
)
func TestListFlavors(t *testing.T) {
client, err := newClient()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
t.Logf("ID\tRegion\tName\tStatus\tCreated")
pager := flavors.ListDetail(client, nil)
count, pages := 0, 0
pager.EachPage(func(page pagination.Page) (bool, error) {
t.Logf("---")
pages++
flavors, err := flavors.ExtractFlavors(page)
if err != nil {
return false, err
}
for _, f := range flavors {
t.Logf("%s\t%s\t%d\t%d\t%d", f.ID, f.Name, f.RAM, f.Disk, f.VCPUs)
}
return true, nil
})
t.Logf("--------\n%d flavors listed on %d pages.", count, pages)
}
func TestGetFlavor(t *testing.T) {
client, err := newClient()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := ComputeChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
flavor, err := flavors.Get(client, choices.FlavorID).Extract()
if err != nil {
t.Fatalf("Unable to get flavor information: %v", err)
}
t.Logf("Flavor: %#v", flavor)
}

View File

@ -0,0 +1,37 @@
// +build acceptance compute images
package v2
import (
"testing"
"github.com/rackspace/gophercloud/openstack/compute/v2/images"
"github.com/rackspace/gophercloud/pagination"
)
func TestListImages(t *testing.T) {
client, err := newClient()
if err != nil {
t.Fatalf("Unable to create a compute: client: %v", err)
}
t.Logf("ID\tRegion\tName\tStatus\tCreated")
pager := images.ListDetail(client, nil)
count, pages := 0, 0
pager.EachPage(func(page pagination.Page) (bool, error) {
pages++
images, err := images.ExtractImages(page)
if err != nil {
return false, err
}
for _, i := range images {
t.Logf("%s\t%s\t%s\t%s", i.ID, i.Name, i.Status, i.Created)
}
return true, nil
})
t.Logf("--------\n%d images listed on %d pages.", count, pages)
}

View File

@ -0,0 +1,74 @@
// +build acceptance
package v2
import (
"crypto/rand"
"crypto/rsa"
"testing"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
th "github.com/rackspace/gophercloud/testhelper"
"code.google.com/p/go.crypto/ssh"
)
const keyName = "gophercloud_test_key_pair"
func TestCreateServerWithKeyPair(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
publicKey := privateKey.PublicKey
pub, err := ssh.NewPublicKey(&publicKey)
th.AssertNoErr(t, err)
pubBytes := ssh.MarshalAuthorizedKey(pub)
pk := string(pubBytes)
kp, err := keypairs.Create(client, keypairs.CreateOpts{
Name: keyName,
PublicKey: pk,
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created key pair: %s\n", kp)
choices, err := ComputeChoicesFromEnv()
th.AssertNoErr(t, err)
name := tools.RandomString("Gophercloud-", 8)
t.Logf("Creating server [%s] with key pair.", name)
serverCreateOpts := servers.CreateOpts{
Name: name,
FlavorRef: choices.FlavorID,
ImageRef: choices.ImageID,
}
server, err := servers.Create(client, keypairs.CreateOptsExt{
serverCreateOpts,
keyName,
}).Extract()
th.AssertNoErr(t, err)
defer servers.Delete(client, server.ID)
if err = waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatalf("Unable to wait for server: %v", err)
}
server, err = servers.Get(client, server.ID).Extract()
t.Logf("Created server: %+v\n", server)
th.AssertNoErr(t, err)
th.AssertEquals(t, server.KeyName, keyName)
t.Logf("Deleting key pair [%s]...", kp.Name)
err = keypairs.Delete(client, keyName).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Deleting server [%s]...", name)
}

View File

@ -0,0 +1,3 @@
// The v2 package contains acceptance tests for the Openstack Compute V2 service.
package v2

View File

@ -0,0 +1,72 @@
// +build acceptance compute defsecrules
package v2
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
dsr "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/defsecrules"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestSecDefRules(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
id := createDefRule(t, client)
listDefRules(t, client)
getDefRule(t, client, id)
deleteDefRule(t, client, id)
}
func createDefRule(t *testing.T, client *gophercloud.ServiceClient) string {
opts := dsr.CreateOpts{
FromPort: tools.RandomInt(80, 89),
ToPort: tools.RandomInt(90, 99),
IPProtocol: "TCP",
CIDR: "0.0.0.0/0",
}
rule, err := dsr.Create(client, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Created default rule %s", rule.ID)
return rule.ID
}
func listDefRules(t *testing.T, client *gophercloud.ServiceClient) {
err := dsr.List(client).EachPage(func(page pagination.Page) (bool, error) {
drList, err := dsr.ExtractDefaultRules(page)
th.AssertNoErr(t, err)
for _, dr := range drList {
t.Logf("Listing default rule %s: Name [%s] From Port [%s] To Port [%s] Protocol [%s]",
dr.ID, dr.FromPort, dr.ToPort, dr.IPProtocol)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func getDefRule(t *testing.T, client *gophercloud.ServiceClient, id string) {
rule, err := dsr.Get(client, id).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting rule %s: %#v", id, rule)
}
func deleteDefRule(t *testing.T, client *gophercloud.ServiceClient, id string) {
err := dsr.Delete(client, id).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Deleted rule %s", id)
}

View File

@ -0,0 +1,177 @@
// +build acceptance compute secgroups
package v2
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestSecGroups(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
serverID, needsDeletion := findServer(t, client)
groupID := createSecGroup(t, client)
listSecGroups(t, client)
newName := tools.RandomString("secgroup_", 5)
updateSecGroup(t, client, groupID, newName)
getSecGroup(t, client, groupID)
addRemoveRules(t, client, groupID)
addServerToSecGroup(t, client, serverID, newName)
removeServerFromSecGroup(t, client, serverID, newName)
if needsDeletion {
servers.Delete(client, serverID)
}
deleteSecGroup(t, client, groupID)
}
func createSecGroup(t *testing.T, client *gophercloud.ServiceClient) string {
opts := secgroups.CreateOpts{
Name: tools.RandomString("secgroup_", 5),
Description: "something",
}
group, err := secgroups.Create(client, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Created secgroup %s %s", group.ID, group.Name)
return group.ID
}
func listSecGroups(t *testing.T, client *gophercloud.ServiceClient) {
err := secgroups.List(client).EachPage(func(page pagination.Page) (bool, error) {
secGrpList, err := secgroups.ExtractSecurityGroups(page)
th.AssertNoErr(t, err)
for _, sg := range secGrpList {
t.Logf("Listing secgroup %s: Name [%s] Desc [%s] TenantID [%s]", sg.ID,
sg.Name, sg.Description, sg.TenantID)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func updateSecGroup(t *testing.T, client *gophercloud.ServiceClient, id, newName string) {
opts := secgroups.UpdateOpts{
Name: newName,
Description: tools.RandomString("dec_", 10),
}
group, err := secgroups.Update(client, id, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Updated %s's name to %s", group.ID, group.Name)
}
func getSecGroup(t *testing.T, client *gophercloud.ServiceClient, id string) {
group, err := secgroups.Get(client, id).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting %s: %#v", id, group)
}
func addRemoveRules(t *testing.T, client *gophercloud.ServiceClient, id string) {
opts := secgroups.CreateRuleOpts{
ParentGroupID: id,
FromPort: 22,
ToPort: 22,
IPProtocol: "TCP",
CIDR: "0.0.0.0/0",
}
rule, err := secgroups.CreateRule(client, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Adding rule %s to group %s", rule.ID, id)
err = secgroups.DeleteRule(client, rule.ID).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Deleted rule %s from group %s", rule.ID, id)
}
func findServer(t *testing.T, client *gophercloud.ServiceClient) (string, bool) {
var serverID string
var needsDeletion bool
err := servers.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
sList, err := servers.ExtractServers(page)
th.AssertNoErr(t, err)
for _, s := range sList {
serverID = s.ID
needsDeletion = false
t.Logf("Found an existing server: ID [%s]", serverID)
break
}
return true, nil
})
th.AssertNoErr(t, err)
if serverID == "" {
t.Log("No server found, creating one")
choices, err := ComputeChoicesFromEnv()
th.AssertNoErr(t, err)
opts := &servers.CreateOpts{
Name: tools.RandomString("secgroup_test_", 5),
ImageRef: choices.ImageID,
FlavorRef: choices.FlavorID,
}
s, err := servers.Create(client, opts).Extract()
th.AssertNoErr(t, err)
serverID = s.ID
t.Logf("Created server %s, waiting for it to build", s.ID)
err = servers.WaitForStatus(client, serverID, "ACTIVE", 300)
th.AssertNoErr(t, err)
needsDeletion = true
}
return serverID, needsDeletion
}
func addServerToSecGroup(t *testing.T, client *gophercloud.ServiceClient, serverID, groupName string) {
err := secgroups.AddServerToGroup(client, serverID, groupName).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Adding group %s to server %s", groupName, serverID)
}
func removeServerFromSecGroup(t *testing.T, client *gophercloud.ServiceClient, serverID, groupName string) {
err := secgroups.RemoveServerFromGroup(client, serverID, groupName).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Removing group %s from server %s", groupName, serverID)
}
func deleteSecGroup(t *testing.T, client *gophercloud.ServiceClient, id string) {
err := secgroups.Delete(client, id).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Deleted group %s", id)
}

View File

@ -0,0 +1,450 @@
// +build acceptance compute servers
package v2
import (
"os"
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/openstack"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestListServers(t *testing.T) {
client, err := newClient()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
t.Logf("ID\tRegion\tName\tStatus\tIPv4\tIPv6")
pager := servers.List(client, servers.ListOpts{})
count, pages := 0, 0
pager.EachPage(func(page pagination.Page) (bool, error) {
pages++
t.Logf("---")
servers, err := servers.ExtractServers(page)
if err != nil {
return false, err
}
for _, s := range servers {
t.Logf("%s\t%s\t%s\t%s\t%s\t\n", s.ID, s.Name, s.Status, s.AccessIPv4, s.AccessIPv6)
count++
}
return true, nil
})
t.Logf("--------\n%d servers listed on %d pages.\n", count, pages)
}
func networkingClient() (*gophercloud.ServiceClient, error) {
opts, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
provider, err := openstack.AuthenticatedClient(opts)
if err != nil {
return nil, err
}
return openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
Name: "neutron",
Region: os.Getenv("OS_REGION_NAME"),
})
}
func createServer(t *testing.T, client *gophercloud.ServiceClient, choices *ComputeChoices) (*servers.Server, error) {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
var network networks.Network
networkingClient, err := networkingClient()
if err != nil {
t.Fatalf("Unable to create a networking client: %v", err)
}
pager := networks.List(networkingClient, networks.ListOpts{Name: "public", Limit: 1})
pager.EachPage(func(page pagination.Page) (bool, error) {
networks, err := networks.ExtractNetworks(page)
if err != nil {
t.Errorf("Failed to extract networks: %v", err)
return false, err
}
if len(networks) == 0 {
t.Fatalf("No networks to attach to server")
return false, err
}
network = networks[0]
return false, nil
})
name := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create server: %s\n", name)
pwd := tools.MakeNewPassword("")
server, err := servers.Create(client, servers.CreateOpts{
Name: name,
FlavorRef: choices.FlavorID,
ImageRef: choices.ImageID,
Networks: []servers.Network{
servers.Network{UUID: network.ID},
},
AdminPass: pwd,
}).Extract()
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
th.AssertEquals(t, pwd, server.AdminPass)
return server, err
}
func TestCreateDestroyServer(t *testing.T) {
choices, err := ComputeChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
client, err := newClient()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := createServer(t, client, choices)
if err != nil {
t.Fatalf("Unable to create server: %v", err)
}
defer func() {
servers.Delete(client, server.ID)
t.Logf("Server deleted.")
}()
if err = waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatalf("Unable to wait for server: %v", err)
}
}
func TestUpdateServer(t *testing.T) {
client, err := newClient()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := ComputeChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
server, err := createServer(t, client, choices)
if err != nil {
t.Fatal(err)
}
defer servers.Delete(client, server.ID)
if err = waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
alternateName := tools.RandomString("ACPTTEST", 16)
for alternateName == server.Name {
alternateName = tools.RandomString("ACPTTEST", 16)
}
t.Logf("Attempting to rename the server to %s.", alternateName)
updated, err := servers.Update(client, server.ID, servers.UpdateOpts{Name: alternateName}).Extract()
if err != nil {
t.Fatalf("Unable to rename server: %v", err)
}
if updated.ID != server.ID {
t.Errorf("Updated server ID [%s] didn't match original server ID [%s]!", updated.ID, server.ID)
}
err = tools.WaitFor(func() (bool, error) {
latest, err := servers.Get(client, updated.ID).Extract()
if err != nil {
return false, err
}
return latest.Name == alternateName, nil
})
}
func TestActionChangeAdminPassword(t *testing.T) {
t.Parallel()
client, err := newClient()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := ComputeChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
server, err := createServer(t, client, choices)
if err != nil {
t.Fatal(err)
}
defer servers.Delete(client, server.ID)
if err = waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
randomPassword := tools.MakeNewPassword(server.AdminPass)
res := servers.ChangeAdminPassword(client, server.ID, randomPassword)
if res.Err != nil {
t.Fatal(err)
}
if err = waitForStatus(client, server, "PASSWORD"); err != nil {
t.Fatal(err)
}
if err = waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
}
func TestActionReboot(t *testing.T) {
t.Parallel()
client, err := newClient()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := ComputeChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
server, err := createServer(t, client, choices)
if err != nil {
t.Fatal(err)
}
defer servers.Delete(client, server.ID)
if err = waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
res := servers.Reboot(client, server.ID, "aldhjflaskhjf")
if res.Err == nil {
t.Fatal("Expected the SDK to provide an ArgumentError here")
}
t.Logf("Attempting reboot of server %s", server.ID)
res = servers.Reboot(client, server.ID, servers.OSReboot)
if res.Err != nil {
t.Fatalf("Unable to reboot server: %v", err)
}
if err = waitForStatus(client, server, "REBOOT"); err != nil {
t.Fatal(err)
}
if err = waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
}
func TestActionRebuild(t *testing.T) {
t.Parallel()
client, err := newClient()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
choices, err := ComputeChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
server, err := createServer(t, client, choices)
if err != nil {
t.Fatal(err)
}
defer servers.Delete(client, server.ID)
if err = waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
t.Logf("Attempting to rebuild server %s", server.ID)
rebuildOpts := servers.RebuildOpts{
Name: tools.RandomString("ACPTTEST", 16),
AdminPass: tools.MakeNewPassword(server.AdminPass),
ImageID: choices.ImageID,
}
rebuilt, err := servers.Rebuild(client, server.ID, rebuildOpts).Extract()
if err != nil {
t.Fatal(err)
}
if rebuilt.ID != server.ID {
t.Errorf("Expected rebuilt server ID of [%s]; got [%s]", server.ID, rebuilt.ID)
}
if err = waitForStatus(client, rebuilt, "REBUILD"); err != nil {
t.Fatal(err)
}
if err = waitForStatus(client, rebuilt, "ACTIVE"); err != nil {
t.Fatal(err)
}
}
func resizeServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server, choices *ComputeChoices) {
if err := waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
t.Logf("Attempting to resize server [%s]", server.ID)
opts := &servers.ResizeOpts{
FlavorRef: choices.FlavorIDResize,
}
if res := servers.Resize(client, server.ID, opts); res.Err != nil {
t.Fatal(res.Err)
}
if err := waitForStatus(client, server, "VERIFY_RESIZE"); err != nil {
t.Fatal(err)
}
}
func TestActionResizeConfirm(t *testing.T) {
t.Parallel()
choices, err := ComputeChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
client, err := newClient()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := createServer(t, client, choices)
if err != nil {
t.Fatal(err)
}
defer servers.Delete(client, server.ID)
resizeServer(t, client, server, choices)
t.Logf("Attempting to confirm resize for server %s", server.ID)
if res := servers.ConfirmResize(client, server.ID); res.Err != nil {
t.Fatal(err)
}
if err = waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
}
func TestActionResizeRevert(t *testing.T) {
t.Parallel()
choices, err := ComputeChoicesFromEnv()
if err != nil {
t.Fatal(err)
}
client, err := newClient()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := createServer(t, client, choices)
if err != nil {
t.Fatal(err)
}
defer servers.Delete(client, server.ID)
resizeServer(t, client, server, choices)
t.Logf("Attempting to revert resize for server %s", server.ID)
if res := servers.RevertResize(client, server.ID); res.Err != nil {
t.Fatal(err)
}
if err = waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
}
func TestServerMetadata(t *testing.T) {
t.Parallel()
choices, err := ComputeChoicesFromEnv()
th.AssertNoErr(t, err)
client, err := newClient()
if err != nil {
t.Fatalf("Unable to create a compute client: %v", err)
}
server, err := createServer(t, client, choices)
if err != nil {
t.Fatal(err)
}
defer servers.Delete(client, server.ID)
if err = waitForStatus(client, server, "ACTIVE"); err != nil {
t.Fatal(err)
}
metadata, err := servers.UpdateMetadata(client, server.ID, servers.MetadataOpts{
"foo": "bar",
"this": "that",
}).Extract()
th.AssertNoErr(t, err)
t.Logf("UpdateMetadata result: %+v\n", metadata)
err = servers.DeleteMetadatum(client, server.ID, "foo").ExtractErr()
th.AssertNoErr(t, err)
metadata, err = servers.CreateMetadatum(client, server.ID, servers.MetadatumOpts{
"foo": "baz",
}).Extract()
th.AssertNoErr(t, err)
t.Logf("CreateMetadatum result: %+v\n", metadata)
metadata, err = servers.Metadatum(client, server.ID, "foo").Extract()
th.AssertNoErr(t, err)
t.Logf("Metadatum result: %+v\n", metadata)
th.AssertEquals(t, "baz", metadata["foo"])
metadata, err = servers.Metadata(client, server.ID).Extract()
th.AssertNoErr(t, err)
t.Logf("Metadata result: %+v\n", metadata)
metadata, err = servers.ResetMetadata(client, server.ID, servers.MetadataOpts{}).Extract()
th.AssertNoErr(t, err)
t.Logf("ResetMetadata result: %+v\n", metadata)
th.AssertDeepEquals(t, map[string]string{}, metadata)
}

View File

@ -0,0 +1,46 @@
// +build acceptance identity
package v2
import (
"testing"
extensions2 "github.com/rackspace/gophercloud/openstack/identity/v2/extensions"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestEnumerateExtensions(t *testing.T) {
service := authenticatedClient(t)
t.Logf("Extensions available on this identity endpoint:")
count := 0
err := extensions2.List(service).EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page %02d ---", count)
extensions, err := extensions2.ExtractExtensions(page)
th.AssertNoErr(t, err)
for i, ext := range extensions {
t.Logf("[%02d] name=[%s] namespace=[%s]", i, ext.Name, ext.Namespace)
t.Logf(" alias=[%s] updated=[%s]", ext.Alias, ext.Updated)
t.Logf(" description=[%s]", ext.Description)
}
count++
return true, nil
})
th.AssertNoErr(t, err)
}
func TestGetExtension(t *testing.T) {
service := authenticatedClient(t)
ext, err := extensions2.Get(service, "OS-KSCRUD").Extract()
th.AssertNoErr(t, err)
th.CheckEquals(t, "OpenStack Keystone User CRUD", ext.Name)
th.CheckEquals(t, "http://docs.openstack.org/identity/api/ext/OS-KSCRUD/v1.0", ext.Namespace)
th.CheckEquals(t, "OS-KSCRUD", ext.Alias)
th.CheckEquals(t, "OpenStack extensions to Keystone v2.0 API enabling User Operations.", ext.Description)
}

View File

@ -0,0 +1,47 @@
// +build acceptance identity
package v2
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
th "github.com/rackspace/gophercloud/testhelper"
)
func v2AuthOptions(t *testing.T) gophercloud.AuthOptions {
// Obtain credentials from the environment.
ao, err := openstack.AuthOptionsFromEnv()
th.AssertNoErr(t, err)
// Trim out unused fields. Prefer authentication by API key to password.
ao.UserID, ao.DomainID, ao.DomainName = "", "", ""
if ao.APIKey != "" {
ao.Password = ""
}
return ao
}
func createClient(t *testing.T, auth bool) *gophercloud.ServiceClient {
ao := v2AuthOptions(t)
provider, err := openstack.NewClient(ao.IdentityEndpoint)
th.AssertNoErr(t, err)
if auth {
err = openstack.AuthenticateV2(provider, ao)
th.AssertNoErr(t, err)
}
return openstack.NewIdentityV2(provider)
}
func unauthenticatedClient(t *testing.T) *gophercloud.ServiceClient {
return createClient(t, false)
}
func authenticatedClient(t *testing.T) *gophercloud.ServiceClient {
return createClient(t, true)
}

View File

@ -0,0 +1 @@
package v2

View File

@ -0,0 +1,58 @@
// +build acceptance identity roles
package v2
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/identity/v2/extensions/admin/roles"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestRoles(t *testing.T) {
client := authenticatedClient(t)
tenantID := findTenant(t, client)
userID := createUser(t, client, tenantID)
roleID := listRoles(t, client)
addUserRole(t, client, tenantID, userID, roleID)
deleteUserRole(t, client, tenantID, userID, roleID)
deleteUser(t, client, userID)
}
func listRoles(t *testing.T, client *gophercloud.ServiceClient) string {
var roleID string
err := roles.List(client).EachPage(func(page pagination.Page) (bool, error) {
roleList, err := roles.ExtractRoles(page)
th.AssertNoErr(t, err)
for _, role := range roleList {
t.Logf("Listing role: ID [%s] Name [%s]", role.ID, role.Name)
roleID = role.ID
}
return true, nil
})
th.AssertNoErr(t, err)
return roleID
}
func addUserRole(t *testing.T, client *gophercloud.ServiceClient, tenantID, userID, roleID string) {
err := roles.AddUserRole(client, tenantID, userID, roleID).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Added role %s to user %s", roleID, userID)
}
func deleteUserRole(t *testing.T, client *gophercloud.ServiceClient, tenantID, userID, roleID string) {
err := roles.DeleteUserRole(client, tenantID, userID, roleID).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Removed role %s from user %s", roleID, userID)
}

View File

@ -0,0 +1,32 @@
// +build acceptance identity
package v2
import (
"testing"
tenants2 "github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestEnumerateTenants(t *testing.T) {
service := authenticatedClient(t)
t.Logf("Tenants to which your current token grants access:")
count := 0
err := tenants2.List(service, nil).EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page %02d ---", count)
tenants, err := tenants2.ExtractTenants(page)
th.AssertNoErr(t, err)
for i, tenant := range tenants {
t.Logf("[%02d] name=[%s] id=[%s] description=[%s] enabled=[%v]",
i, tenant.Name, tenant.ID, tenant.Description, tenant.Enabled)
}
count++
return true, nil
})
th.AssertNoErr(t, err)
}

View File

@ -0,0 +1,38 @@
// +build acceptance identity
package v2
import (
"testing"
tokens2 "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestAuthenticate(t *testing.T) {
ao := v2AuthOptions(t)
service := unauthenticatedClient(t)
// Authenticated!
result := tokens2.Create(service, tokens2.WrapOptions(ao))
// Extract and print the token.
token, err := result.ExtractToken()
th.AssertNoErr(t, err)
t.Logf("Acquired token: [%s]", token.ID)
t.Logf("The token will expire at: [%s]", token.ExpiresAt.String())
t.Logf("The token is valid for tenant: [%#v]", token.Tenant)
// Extract and print the service catalog.
catalog, err := result.ExtractServiceCatalog()
th.AssertNoErr(t, err)
t.Logf("Acquired service catalog listing [%d] services", len(catalog.Entries))
for i, entry := range catalog.Entries {
t.Logf("[%02d]: name=[%s], type=[%s]", i, entry.Name, entry.Type)
for _, endpoint := range entry.Endpoints {
t.Logf(" - region=[%s] publicURL=[%s]", endpoint.Region, endpoint.PublicURL)
}
}
}

View File

@ -0,0 +1,127 @@
// +build acceptance identity
package v2
import (
"strconv"
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
"github.com/rackspace/gophercloud/openstack/identity/v2/users"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestUsers(t *testing.T) {
client := authenticatedClient(t)
tenantID := findTenant(t, client)
userID := createUser(t, client, tenantID)
listUsers(t, client)
getUser(t, client, userID)
updateUser(t, client, userID)
listUserRoles(t, client, tenantID, userID)
deleteUser(t, client, userID)
}
func findTenant(t *testing.T, client *gophercloud.ServiceClient) string {
var tenantID string
err := tenants.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
tenantList, err := tenants.ExtractTenants(page)
th.AssertNoErr(t, err)
for _, t := range tenantList {
tenantID = t.ID
break
}
return true, nil
})
th.AssertNoErr(t, err)
return tenantID
}
func createUser(t *testing.T, client *gophercloud.ServiceClient, tenantID string) string {
t.Log("Creating user")
opts := users.CreateOpts{
Name: tools.RandomString("user_", 5),
Enabled: users.Disabled,
TenantID: tenantID,
Email: "new_user@foo.com",
}
user, err := users.Create(client, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Created user %s on tenant %s", user.ID, tenantID)
return user.ID
}
func listUsers(t *testing.T, client *gophercloud.ServiceClient) {
err := users.List(client).EachPage(func(page pagination.Page) (bool, error) {
userList, err := users.ExtractUsers(page)
th.AssertNoErr(t, err)
for _, user := range userList {
t.Logf("Listing user: ID [%s] Name [%s] Email [%s] Enabled? [%s]",
user.ID, user.Name, user.Email, strconv.FormatBool(user.Enabled))
}
return true, nil
})
th.AssertNoErr(t, err)
}
func getUser(t *testing.T, client *gophercloud.ServiceClient, userID string) {
_, err := users.Get(client, userID).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting user %s", userID)
}
func updateUser(t *testing.T, client *gophercloud.ServiceClient, userID string) {
opts := users.UpdateOpts{Name: tools.RandomString("new_name", 5), Email: "new@foo.com"}
user, err := users.Update(client, userID, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Updated user %s: Name [%s] Email [%s]", userID, user.Name, user.Email)
}
func listUserRoles(t *testing.T, client *gophercloud.ServiceClient, tenantID, userID string) {
count := 0
err := users.ListRoles(client, tenantID, userID).EachPage(func(page pagination.Page) (bool, error) {
count++
roleList, err := users.ExtractRoles(page)
th.AssertNoErr(t, err)
t.Logf("Listing roles for user %s", userID)
for _, r := range roleList {
t.Logf("- %s (%s)", r.Name, r.ID)
}
return true, nil
})
if count == 0 {
t.Logf("No roles for user %s", userID)
}
th.AssertNoErr(t, err)
}
func deleteUser(t *testing.T, client *gophercloud.ServiceClient, userID string) {
res := users.Delete(client, userID)
th.AssertNoErr(t, res.Err)
t.Logf("Deleted user %s", userID)
}

View File

@ -0,0 +1,111 @@
// +build acceptance
package v3
import (
"testing"
"github.com/rackspace/gophercloud"
endpoints3 "github.com/rackspace/gophercloud/openstack/identity/v3/endpoints"
services3 "github.com/rackspace/gophercloud/openstack/identity/v3/services"
"github.com/rackspace/gophercloud/pagination"
)
func TestListEndpoints(t *testing.T) {
// Create a service client.
serviceClient := createAuthenticatedClient(t)
if serviceClient == nil {
return
}
// Use the service to list all available endpoints.
pager := endpoints3.List(serviceClient, endpoints3.ListOpts{})
err := pager.EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page ---")
endpoints, err := endpoints3.ExtractEndpoints(page)
if err != nil {
t.Fatalf("Error extracting endpoings: %v", err)
}
for _, endpoint := range endpoints {
t.Logf("Endpoint: %8s %10s %9s %s",
endpoint.ID,
endpoint.Availability,
endpoint.Name,
endpoint.URL)
}
return true, nil
})
if err != nil {
t.Errorf("Unexpected error while iterating endpoint pages: %v", err)
}
}
func TestNavigateCatalog(t *testing.T) {
// Create a service client.
client := createAuthenticatedClient(t)
if client == nil {
return
}
var compute *services3.Service
var endpoint *endpoints3.Endpoint
// Discover the service we're interested in.
servicePager := services3.List(client, services3.ListOpts{ServiceType: "compute"})
err := servicePager.EachPage(func(page pagination.Page) (bool, error) {
part, err := services3.ExtractServices(page)
if err != nil {
return false, err
}
if compute != nil {
t.Fatalf("Expected one service, got more than one page")
return false, nil
}
if len(part) != 1 {
t.Fatalf("Expected one service, got %d", len(part))
return false, nil
}
compute = &part[0]
return true, nil
})
if err != nil {
t.Fatalf("Unexpected error iterating pages: %v", err)
}
if compute == nil {
t.Fatalf("No compute service found.")
}
// Enumerate the endpoints available for this service.
computePager := endpoints3.List(client, endpoints3.ListOpts{
Availability: gophercloud.AvailabilityPublic,
ServiceID: compute.ID,
})
err = computePager.EachPage(func(page pagination.Page) (bool, error) {
part, err := endpoints3.ExtractEndpoints(page)
if err != nil {
return false, err
}
if endpoint != nil {
t.Fatalf("Expected one endpoint, got more than one page")
return false, nil
}
if len(part) != 1 {
t.Fatalf("Expected one endpoint, got %d", len(part))
return false, nil
}
endpoint = &part[0]
return true, nil
})
if endpoint == nil {
t.Fatalf("No endpoint found.")
}
t.Logf("Success. The compute endpoint is at %s.", endpoint.URL)
}

View File

@ -0,0 +1,39 @@
// +build acceptance
package v3
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
th "github.com/rackspace/gophercloud/testhelper"
)
func createAuthenticatedClient(t *testing.T) *gophercloud.ServiceClient {
// Obtain credentials from the environment.
ao, err := openstack.AuthOptionsFromEnv()
th.AssertNoErr(t, err)
// Trim out unused fields.
ao.Username, ao.TenantID, ao.TenantName = "", "", ""
if ao.UserID == "" {
t.Logf("Skipping identity v3 tests because no OS_USERID is present.")
return nil
}
// Create a client and manually authenticate against v3.
providerClient, err := openstack.NewClient(ao.IdentityEndpoint)
if err != nil {
t.Fatalf("Unable to instantiate client: %v", err)
}
err = openstack.AuthenticateV3(providerClient, ao)
if err != nil {
t.Fatalf("Unable to authenticate against identity v3: %v", err)
}
// Create a service client.
return openstack.NewIdentityV3(providerClient)
}

View File

@ -0,0 +1 @@
package v3

View File

@ -0,0 +1,36 @@
// +build acceptance
package v3
import (
"testing"
services3 "github.com/rackspace/gophercloud/openstack/identity/v3/services"
"github.com/rackspace/gophercloud/pagination"
)
func TestListServices(t *testing.T) {
// Create a service client.
serviceClient := createAuthenticatedClient(t)
if serviceClient == nil {
return
}
// Use the client to list all available services.
pager := services3.List(serviceClient, services3.ListOpts{})
err := pager.EachPage(func(page pagination.Page) (bool, error) {
parts, err := services3.ExtractServices(page)
if err != nil {
return false, err
}
t.Logf("--- Page ---")
for _, service := range parts {
t.Logf("Service: %32s %15s %10s %s", service.ID, service.Type, service.Name, *service.Description)
}
return true, nil
})
if err != nil {
t.Errorf("Unexpected error traversing pages: %v", err)
}
}

View File

@ -0,0 +1,42 @@
// +build acceptance
package v3
import (
"testing"
"github.com/rackspace/gophercloud/openstack"
tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
)
func TestGetToken(t *testing.T) {
// Obtain credentials from the environment.
ao, err := openstack.AuthOptionsFromEnv()
if err != nil {
t.Fatalf("Unable to acquire credentials: %v", err)
}
// Trim out unused fields. Skip if we don't have a UserID.
ao.Username, ao.TenantID, ao.TenantName = "", "", ""
if ao.UserID == "" {
t.Logf("Skipping identity v3 tests because no OS_USERID is present.")
return
}
// Create an unauthenticated client.
provider, err := openstack.NewClient(ao.IdentityEndpoint)
if err != nil {
t.Fatalf("Unable to instantiate client: %v", err)
}
// Create a service client.
service := openstack.NewIdentityV3(provider)
// Use the service to create a token.
token, err := tokens3.Create(service, ao, nil).Extract()
if err != nil {
t.Fatalf("Unable to get token: %v", err)
}
t.Logf("Acquired token: %s", token.ID)
}

View File

@ -0,0 +1,51 @@
// +build acceptance networking
package v2
import (
"testing"
"github.com/rackspace/gophercloud/openstack/networking/v2/apiversions"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestListAPIVersions(t *testing.T) {
Setup(t)
defer Teardown()
pager := apiversions.ListVersions(Client)
err := pager.EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page ---")
versions, err := apiversions.ExtractAPIVersions(page)
th.AssertNoErr(t, err)
for _, v := range versions {
t.Logf("API Version: ID [%s] Status [%s]", v.ID, v.Status)
}
return true, nil
})
th.CheckNoErr(t, err)
}
func TestListAPIResources(t *testing.T) {
Setup(t)
defer Teardown()
pager := apiversions.ListVersionResources(Client, "v2.0")
err := pager.EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page ---")
vrs, err := apiversions.ExtractVersionResources(page)
th.AssertNoErr(t, err)
for _, vr := range vrs {
t.Logf("Network: Name [%s] Collection [%s]", vr.Name, vr.Collection)
}
return true, nil
})
th.CheckNoErr(t, err)
}

View File

@ -0,0 +1,39 @@
package v2
import (
"os"
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
th "github.com/rackspace/gophercloud/testhelper"
)
var Client *gophercloud.ServiceClient
func NewClient() (*gophercloud.ServiceClient, error) {
opts, err := openstack.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
provider, err := openstack.AuthenticatedClient(opts)
if err != nil {
return nil, err
}
return openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
Name: "neutron",
Region: os.Getenv("OS_REGION_NAME"),
})
}
func Setup(t *testing.T) {
client, err := NewClient()
th.AssertNoErr(t, err)
Client = client
}
func Teardown() {
Client = nil
}

View File

@ -0,0 +1,45 @@
// +build acceptance networking
package v2
import (
"testing"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestListExts(t *testing.T) {
Setup(t)
defer Teardown()
pager := extensions.List(Client)
err := pager.EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page ---")
exts, err := extensions.ExtractExtensions(page)
th.AssertNoErr(t, err)
for _, ext := range exts {
t.Logf("Extension: Name [%s] Description [%s]", ext.Name, ext.Description)
}
return true, nil
})
th.CheckNoErr(t, err)
}
func TestGetExt(t *testing.T) {
Setup(t)
defer Teardown()
ext, err := extensions.Get(Client, "service-type").Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, ext.Updated, "2013-01-20T00:00:00-00:00")
th.AssertEquals(t, ext.Name, "Neutron Service Type Management")
th.AssertEquals(t, ext.Namespace, "http://docs.openstack.org/ext/neutron/service-type/api/v1.0")
th.AssertEquals(t, ext.Alias, "service-type")
th.AssertEquals(t, ext.Description, "API for retrieving service providers for Neutron advanced services")
}

View File

@ -0,0 +1,300 @@
// +build acceptance networking layer3ext
package extensions
import (
"testing"
base "github.com/rackspace/gophercloud/acceptance/openstack/networking/v2"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/external"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/openstack/networking/v2/ports"
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
const (
cidr1 = "10.0.0.1/24"
cidr2 = "20.0.0.1/24"
)
func TestAll(t *testing.T) {
base.Setup(t)
defer base.Teardown()
testRouter(t)
testFloatingIP(t)
}
func testRouter(t *testing.T) {
// Setup: Create network
networkID := createNetwork(t)
// Create router
routerID := createRouter(t, networkID)
// Lists routers
listRouters(t)
// Update router
updateRouter(t, routerID)
// Get router
getRouter(t, routerID)
// Create new subnet. Note: this subnet will be deleted when networkID is deleted
subnetID := createSubnet(t, networkID, cidr2)
// Add interface
addInterface(t, routerID, subnetID)
// Remove interface
removeInterface(t, routerID, subnetID)
// Delete router
deleteRouter(t, routerID)
// Cleanup
deleteNetwork(t, networkID)
}
func testFloatingIP(t *testing.T) {
// Setup external network
extNetworkID := createNetwork(t)
// Setup internal network, subnet and port
intNetworkID, subnetID, portID := createInternalTopology(t)
// Now the important part: we need to allow the external network to talk to
// the internal subnet. For this we need a router that has an interface to
// the internal subnet.
routerID := bridgeIntSubnetWithExtNetwork(t, extNetworkID, subnetID)
// Create floating IP
ipID := createFloatingIP(t, extNetworkID, portID)
// Get floating IP
getFloatingIP(t, ipID)
// Update floating IP
updateFloatingIP(t, ipID, portID)
// Delete floating IP
deleteFloatingIP(t, ipID)
// Remove the internal subnet interface
removeInterface(t, routerID, subnetID)
// Delete router and external network
deleteRouter(t, routerID)
deleteNetwork(t, extNetworkID)
// Delete internal port and network
deletePort(t, portID)
deleteNetwork(t, intNetworkID)
}
func createNetwork(t *testing.T) string {
t.Logf("Creating a network")
asu := true
opts := external.CreateOpts{
Parent: networks.CreateOpts{Name: "sample_network", AdminStateUp: &asu},
External: true,
}
n, err := networks.Create(base.Client, opts).Extract()
th.AssertNoErr(t, err)
if n.ID == "" {
t.Fatalf("No ID returned when creating a network")
}
createSubnet(t, n.ID, cidr1)
t.Logf("Network created: ID [%s]", n.ID)
return n.ID
}
func deleteNetwork(t *testing.T, networkID string) {
t.Logf("Deleting network %s", networkID)
networks.Delete(base.Client, networkID)
}
func deletePort(t *testing.T, portID string) {
t.Logf("Deleting port %s", portID)
ports.Delete(base.Client, portID)
}
func createInternalTopology(t *testing.T) (string, string, string) {
t.Logf("Creating an internal network (for port)")
opts := networks.CreateOpts{Name: "internal_network"}
n, err := networks.Create(base.Client, opts).Extract()
th.AssertNoErr(t, err)
// A subnet is also needed
subnetID := createSubnet(t, n.ID, cidr2)
t.Logf("Creating an internal port on network %s", n.ID)
p, err := ports.Create(base.Client, ports.CreateOpts{
NetworkID: n.ID,
Name: "fixed_internal_port",
}).Extract()
th.AssertNoErr(t, err)
return n.ID, subnetID, p.ID
}
func bridgeIntSubnetWithExtNetwork(t *testing.T, networkID, subnetID string) string {
// Create router with external gateway info
routerID := createRouter(t, networkID)
// Add interface for internal subnet
addInterface(t, routerID, subnetID)
return routerID
}
func createSubnet(t *testing.T, networkID, cidr string) string {
t.Logf("Creating a subnet for network %s", networkID)
iFalse := false
s, err := subnets.Create(base.Client, subnets.CreateOpts{
NetworkID: networkID,
CIDR: cidr,
IPVersion: subnets.IPv4,
Name: "my_subnet",
EnableDHCP: &iFalse,
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Subnet created: ID [%s]", s.ID)
return s.ID
}
func createRouter(t *testing.T, networkID string) string {
t.Logf("Creating a router for network %s", networkID)
asu := false
gwi := routers.GatewayInfo{NetworkID: networkID}
r, err := routers.Create(base.Client, routers.CreateOpts{
Name: "foo_router",
AdminStateUp: &asu,
GatewayInfo: &gwi,
}).Extract()
th.AssertNoErr(t, err)
if r.ID == "" {
t.Fatalf("No ID returned when creating a router")
}
t.Logf("Router created: ID [%s]", r.ID)
return r.ID
}
func listRouters(t *testing.T) {
pager := routers.List(base.Client, routers.ListOpts{})
err := pager.EachPage(func(page pagination.Page) (bool, error) {
routerList, err := routers.ExtractRouters(page)
th.AssertNoErr(t, err)
for _, r := range routerList {
t.Logf("Listing router: ID [%s] Name [%s] Status [%s] GatewayInfo [%#v]",
r.ID, r.Name, r.Status, r.GatewayInfo)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func updateRouter(t *testing.T, routerID string) {
_, err := routers.Update(base.Client, routerID, routers.UpdateOpts{
Name: "another_name",
}).Extract()
th.AssertNoErr(t, err)
}
func getRouter(t *testing.T, routerID string) {
r, err := routers.Get(base.Client, routerID).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting router: ID [%s] Name [%s] Status [%s]", r.ID, r.Name, r.Status)
}
func addInterface(t *testing.T, routerID, subnetID string) {
ir, err := routers.AddInterface(base.Client, routerID, routers.InterfaceOpts{SubnetID: subnetID}).Extract()
th.AssertNoErr(t, err)
t.Logf("Interface added to router %s: SubnetID [%s] PortID [%s]", routerID, ir.SubnetID, ir.PortID)
}
func removeInterface(t *testing.T, routerID, subnetID string) {
ir, err := routers.RemoveInterface(base.Client, routerID, routers.InterfaceOpts{SubnetID: subnetID}).Extract()
th.AssertNoErr(t, err)
t.Logf("Interface %s removed from %s", ir.ID, routerID)
}
func deleteRouter(t *testing.T, routerID string) {
t.Logf("Deleting router %s", routerID)
res := routers.Delete(base.Client, routerID)
th.AssertNoErr(t, res.Err)
}
func createFloatingIP(t *testing.T, networkID, portID string) string {
t.Logf("Creating floating IP on network [%s] with port [%s]", networkID, portID)
opts := floatingips.CreateOpts{
FloatingNetworkID: networkID,
PortID: portID,
}
ip, err := floatingips.Create(base.Client, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Floating IP created: ID [%s] Status [%s] Fixed (internal) IP: [%s] Floating (external) IP: [%s]",
ip.ID, ip.Status, ip.FixedIP, ip.FloatingIP)
return ip.ID
}
func getFloatingIP(t *testing.T, ipID string) {
ip, err := floatingips.Get(base.Client, ipID).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting floating IP: ID [%s] Status [%s]", ip.ID, ip.Status)
}
func updateFloatingIP(t *testing.T, ipID, portID string) {
t.Logf("Disassociate all ports from IP %s", ipID)
_, err := floatingips.Update(base.Client, ipID, floatingips.UpdateOpts{PortID: ""}).Extract()
th.AssertNoErr(t, err)
t.Logf("Re-associate the port %s", portID)
_, err = floatingips.Update(base.Client, ipID, floatingips.UpdateOpts{PortID: portID}).Extract()
th.AssertNoErr(t, err)
}
func deleteFloatingIP(t *testing.T, ipID string) {
t.Logf("Deleting IP %s", ipID)
res := floatingips.Delete(base.Client, ipID)
th.AssertNoErr(t, res.Err)
}

View File

@ -0,0 +1,78 @@
package lbaas
import (
"testing"
base "github.com/rackspace/gophercloud/acceptance/openstack/networking/v2"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets"
th "github.com/rackspace/gophercloud/testhelper"
)
func SetupTopology(t *testing.T) (string, string) {
// create network
n, err := networks.Create(base.Client, networks.CreateOpts{Name: "tmp_network"}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created network %s", n.ID)
// create subnet
s, err := subnets.Create(base.Client, subnets.CreateOpts{
NetworkID: n.ID,
CIDR: "192.168.199.0/24",
IPVersion: subnets.IPv4,
Name: "tmp_subnet",
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created subnet %s", s.ID)
return n.ID, s.ID
}
func DeleteTopology(t *testing.T, networkID string) {
res := networks.Delete(base.Client, networkID)
th.AssertNoErr(t, res.Err)
t.Logf("Deleted network %s", networkID)
}
func CreatePool(t *testing.T, subnetID string) string {
p, err := pools.Create(base.Client, pools.CreateOpts{
LBMethod: pools.LBMethodRoundRobin,
Protocol: "HTTP",
Name: "tmp_pool",
SubnetID: subnetID,
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created pool %s", p.ID)
return p.ID
}
func DeletePool(t *testing.T, poolID string) {
res := pools.Delete(base.Client, poolID)
th.AssertNoErr(t, res.Err)
t.Logf("Deleted pool %s", poolID)
}
func CreateMonitor(t *testing.T) string {
m, err := monitors.Create(base.Client, monitors.CreateOpts{
Delay: 10,
Timeout: 10,
MaxRetries: 3,
Type: monitors.TypeHTTP,
ExpectedCodes: "200",
URLPath: "/login",
HTTPMethod: "GET",
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created monitor ID [%s]", m.ID)
return m.ID
}

View File

@ -0,0 +1,95 @@
// +build acceptance networking lbaas lbaasmember
package lbaas
import (
"testing"
base "github.com/rackspace/gophercloud/acceptance/openstack/networking/v2"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestMembers(t *testing.T) {
base.Setup(t)
defer base.Teardown()
// setup
networkID, subnetID := SetupTopology(t)
poolID := CreatePool(t, subnetID)
// create member
memberID := createMember(t, poolID)
// list members
listMembers(t)
// update member
updateMember(t, memberID)
// get member
getMember(t, memberID)
// delete member
deleteMember(t, memberID)
// teardown
DeletePool(t, poolID)
DeleteTopology(t, networkID)
}
func createMember(t *testing.T, poolID string) string {
m, err := members.Create(base.Client, members.CreateOpts{
Address: "192.168.199.1",
ProtocolPort: 8080,
PoolID: poolID,
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created member: ID [%s] Status [%s] Weight [%d] Address [%s] Port [%d]",
m.ID, m.Status, m.Weight, m.Address, m.ProtocolPort)
return m.ID
}
func listMembers(t *testing.T) {
err := members.List(base.Client, members.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
memberList, err := members.ExtractMembers(page)
if err != nil {
t.Errorf("Failed to extract members: %v", err)
return false, err
}
for _, m := range memberList {
t.Logf("Listing member: ID [%s] Status [%s]", m.ID, m.Status)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func updateMember(t *testing.T, memberID string) {
m, err := members.Update(base.Client, memberID, members.UpdateOpts{AdminStateUp: true}).Extract()
th.AssertNoErr(t, err)
t.Logf("Updated member ID [%s]", m.ID)
}
func getMember(t *testing.T, memberID string) {
m, err := members.Get(base.Client, memberID).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting member ID [%s]", m.ID)
}
func deleteMember(t *testing.T, memberID string) {
res := members.Delete(base.Client, memberID)
th.AssertNoErr(t, res.Err)
t.Logf("Deleted member %s", memberID)
}

View File

@ -0,0 +1,77 @@
// +build acceptance networking lbaas lbaasmonitor
package lbaas
import (
"testing"
base "github.com/rackspace/gophercloud/acceptance/openstack/networking/v2"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestMonitors(t *testing.T) {
base.Setup(t)
defer base.Teardown()
// create monitor
monitorID := CreateMonitor(t)
// list monitors
listMonitors(t)
// update monitor
updateMonitor(t, monitorID)
// get monitor
getMonitor(t, monitorID)
// delete monitor
deleteMonitor(t, monitorID)
}
func listMonitors(t *testing.T) {
err := monitors.List(base.Client, monitors.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
monitorList, err := monitors.ExtractMonitors(page)
if err != nil {
t.Errorf("Failed to extract monitors: %v", err)
return false, err
}
for _, m := range monitorList {
t.Logf("Listing monitor: ID [%s] Type [%s] Delay [%ds] Timeout [%d] Retries [%d] Status [%s]",
m.ID, m.Type, m.Delay, m.Timeout, m.MaxRetries, m.Status)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func updateMonitor(t *testing.T, monitorID string) {
opts := monitors.UpdateOpts{Delay: 10, Timeout: 10, MaxRetries: 3}
m, err := monitors.Update(base.Client, monitorID, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Updated monitor ID [%s]", m.ID)
}
func getMonitor(t *testing.T, monitorID string) {
m, err := monitors.Get(base.Client, monitorID).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting monitor ID [%s]: URL path [%s] HTTP Method [%s] Accepted codes [%s]",
m.ID, m.URLPath, m.HTTPMethod, m.ExpectedCodes)
}
func deleteMonitor(t *testing.T, monitorID string) {
res := monitors.Delete(base.Client, monitorID)
th.AssertNoErr(t, res.Err)
t.Logf("Deleted monitor %s", monitorID)
}

View File

@ -0,0 +1,98 @@
// +build acceptance networking lbaas lbaaspool
package lbaas
import (
"testing"
base "github.com/rackspace/gophercloud/acceptance/openstack/networking/v2"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestPools(t *testing.T) {
base.Setup(t)
defer base.Teardown()
// setup
networkID, subnetID := SetupTopology(t)
// create pool
poolID := CreatePool(t, subnetID)
// list pools
listPools(t)
// update pool
updatePool(t, poolID)
// get pool
getPool(t, poolID)
// create monitor
monitorID := CreateMonitor(t)
// associate health monitor
associateMonitor(t, poolID, monitorID)
// disassociate health monitor
disassociateMonitor(t, poolID, monitorID)
// delete pool
DeletePool(t, poolID)
// teardown
DeleteTopology(t, networkID)
}
func listPools(t *testing.T) {
err := pools.List(base.Client, pools.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
poolList, err := pools.ExtractPools(page)
if err != nil {
t.Errorf("Failed to extract pools: %v", err)
return false, err
}
for _, p := range poolList {
t.Logf("Listing pool: ID [%s] Name [%s] Status [%s] LB algorithm [%s]", p.ID, p.Name, p.Status, p.LBMethod)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func updatePool(t *testing.T, poolID string) {
opts := pools.UpdateOpts{Name: "SuperPool", LBMethod: pools.LBMethodLeastConnections}
p, err := pools.Update(base.Client, poolID, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Updated pool ID [%s]", p.ID)
}
func getPool(t *testing.T, poolID string) {
p, err := pools.Get(base.Client, poolID).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting pool ID [%s]", p.ID)
}
func associateMonitor(t *testing.T, poolID, monitorID string) {
res := pools.AssociateMonitor(base.Client, poolID, monitorID)
th.AssertNoErr(t, res.Err)
t.Logf("Associated pool %s with monitor %s", poolID, monitorID)
}
func disassociateMonitor(t *testing.T, poolID, monitorID string) {
res := pools.DisassociateMonitor(base.Client, poolID, monitorID)
th.AssertNoErr(t, res.Err)
t.Logf("Disassociated pool %s with monitor %s", poolID, monitorID)
}

View File

@ -0,0 +1,101 @@
// +build acceptance networking lbaas lbaasvip
package lbaas
import (
"testing"
base "github.com/rackspace/gophercloud/acceptance/openstack/networking/v2"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestVIPs(t *testing.T) {
base.Setup(t)
defer base.Teardown()
// setup
networkID, subnetID := SetupTopology(t)
poolID := CreatePool(t, subnetID)
// create VIP
VIPID := createVIP(t, subnetID, poolID)
// list VIPs
listVIPs(t)
// update VIP
updateVIP(t, VIPID)
// get VIP
getVIP(t, VIPID)
// delete VIP
deleteVIP(t, VIPID)
// teardown
DeletePool(t, poolID)
DeleteTopology(t, networkID)
}
func createVIP(t *testing.T, subnetID, poolID string) string {
p, err := vips.Create(base.Client, vips.CreateOpts{
Protocol: "HTTP",
Name: "New_VIP",
AdminStateUp: vips.Up,
SubnetID: subnetID,
PoolID: poolID,
ProtocolPort: 80,
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created pool %s", p.ID)
return p.ID
}
func listVIPs(t *testing.T) {
err := vips.List(base.Client, vips.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
vipList, err := vips.ExtractVIPs(page)
if err != nil {
t.Errorf("Failed to extract VIPs: %v", err)
return false, err
}
for _, vip := range vipList {
t.Logf("Listing VIP: ID [%s] Name [%s] Address [%s] Port [%s] Connection Limit [%d]",
vip.ID, vip.Name, vip.Address, vip.ProtocolPort, vip.ConnLimit)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func updateVIP(t *testing.T, VIPID string) {
i1000 := 1000
_, err := vips.Update(base.Client, VIPID, vips.UpdateOpts{ConnLimit: &i1000}).Extract()
th.AssertNoErr(t, err)
t.Logf("Updated VIP ID [%s]", VIPID)
}
func getVIP(t *testing.T, VIPID string) {
vip, err := vips.Get(base.Client, VIPID).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting VIP ID [%s]: Status [%s]", vip.ID, vip.Status)
}
func deleteVIP(t *testing.T, VIPID string) {
res := vips.Delete(base.Client, VIPID)
th.AssertNoErr(t, res.Err)
t.Logf("Deleted VIP %s", VIPID)
}

View File

@ -0,0 +1 @@
package extensions

View File

@ -0,0 +1,68 @@
// +build acceptance networking
package extensions
import (
"strconv"
"testing"
base "github.com/rackspace/gophercloud/acceptance/openstack/networking/v2"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestNetworkCRUDOperations(t *testing.T) {
base.Setup(t)
defer base.Teardown()
// Create a network
n, err := networks.Create(base.Client, networks.CreateOpts{Name: "sample_network", AdminStateUp: networks.Up}).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, n.Name, "sample_network")
th.AssertEquals(t, n.AdminStateUp, true)
networkID := n.ID
// List networks
pager := networks.List(base.Client, networks.ListOpts{Limit: 2})
err = pager.EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page ---")
networkList, err := networks.ExtractNetworks(page)
th.AssertNoErr(t, err)
for _, n := range networkList {
t.Logf("Network: ID [%s] Name [%s] Status [%s] Is shared? [%s]",
n.ID, n.Name, n.Status, strconv.FormatBool(n.Shared))
}
return true, nil
})
th.CheckNoErr(t, err)
// Get a network
if networkID == "" {
t.Fatalf("In order to retrieve a network, the NetworkID must be set")
}
n, err = networks.Get(base.Client, networkID).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, n.Status, "ACTIVE")
th.AssertDeepEquals(t, n.Subnets, []string{})
th.AssertEquals(t, n.Name, "sample_network")
th.AssertEquals(t, n.AdminStateUp, true)
th.AssertEquals(t, n.Shared, false)
th.AssertEquals(t, n.ID, networkID)
// Update network
n, err = networks.Update(base.Client, networkID, networks.UpdateOpts{Name: "new_network_name"}).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, n.Name, "new_network_name")
// Delete network
res := networks.Delete(base.Client, networkID)
th.AssertNoErr(t, res.Err)
}
func TestCreateMultipleNetworks(t *testing.T) {
//networks.CreateMany()
}

View File

@ -0,0 +1,171 @@
// +build acceptance networking security
package extensions
import (
"testing"
base "github.com/rackspace/gophercloud/acceptance/openstack/networking/v2"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/openstack/networking/v2/ports"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestSecurityGroups(t *testing.T) {
base.Setup(t)
defer base.Teardown()
// create security group
groupID := createSecGroup(t)
// delete security group
defer deleteSecGroup(t, groupID)
// list security group
listSecGroups(t)
// get security group
getSecGroup(t, groupID)
// create port with security group
networkID, portID := createPort(t, groupID)
// teardown
defer deleteNetwork(t, networkID)
// delete port
defer deletePort(t, portID)
}
func TestSecurityGroupRules(t *testing.T) {
base.Setup(t)
defer base.Teardown()
// create security group
groupID := createSecGroup(t)
defer deleteSecGroup(t, groupID)
// create security group rule
ruleID := createSecRule(t, groupID)
// delete security group rule
defer deleteSecRule(t, ruleID)
// list security group rule
listSecRules(t)
// get security group rule
getSecRule(t, ruleID)
}
func createSecGroup(t *testing.T) string {
sg, err := groups.Create(base.Client, groups.CreateOpts{
Name: "new-webservers",
Description: "security group for webservers",
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created security group %s", sg.ID)
return sg.ID
}
func listSecGroups(t *testing.T) {
err := groups.List(base.Client, groups.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
list, err := groups.ExtractGroups(page)
if err != nil {
t.Errorf("Failed to extract secgroups: %v", err)
return false, err
}
for _, sg := range list {
t.Logf("Listing security group: ID [%s] Name [%s]", sg.ID, sg.Name)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func getSecGroup(t *testing.T, id string) {
sg, err := groups.Get(base.Client, id).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting security group: ID [%s] Name [%s] Description [%s]", sg.ID, sg.Name, sg.Description)
}
func createPort(t *testing.T, groupID string) (string, string) {
n, err := networks.Create(base.Client, networks.CreateOpts{Name: "tmp_network"}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created network %s", n.ID)
opts := ports.CreateOpts{
NetworkID: n.ID,
Name: "my_port",
SecurityGroups: []string{groupID},
}
p, err := ports.Create(base.Client, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Created port %s with security group %s", p.ID, groupID)
return n.ID, p.ID
}
func deleteSecGroup(t *testing.T, groupID string) {
res := groups.Delete(base.Client, groupID)
th.AssertNoErr(t, res.Err)
t.Logf("Deleted security group %s", groupID)
}
func createSecRule(t *testing.T, groupID string) string {
r, err := rules.Create(base.Client, rules.CreateOpts{
Direction: "ingress",
PortRangeMin: 80,
EtherType: "IPv4",
PortRangeMax: 80,
Protocol: "tcp",
SecGroupID: groupID,
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created security group rule %s", r.ID)
return r.ID
}
func listSecRules(t *testing.T) {
err := rules.List(base.Client, rules.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
list, err := rules.ExtractRules(page)
if err != nil {
t.Errorf("Failed to extract sec rules: %v", err)
return false, err
}
for _, r := range list {
t.Logf("Listing security rule: ID [%s]", r.ID)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func getSecRule(t *testing.T, id string) {
r, err := rules.Get(base.Client, id).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting security rule: ID [%s] Direction [%s] EtherType [%s] Protocol [%s]",
r.ID, r.Direction, r.EtherType, r.Protocol)
}
func deleteSecRule(t *testing.T, id string) {
res := rules.Delete(base.Client, id)
th.AssertNoErr(t, res.Err)
t.Logf("Deleted security rule %s", id)
}

View File

@ -0,0 +1,68 @@
// +build acceptance networking
package v2
import (
"strconv"
"testing"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestNetworkCRUDOperations(t *testing.T) {
Setup(t)
defer Teardown()
// Create a network
n, err := networks.Create(Client, networks.CreateOpts{Name: "sample_network", AdminStateUp: networks.Up}).Extract()
th.AssertNoErr(t, err)
defer networks.Delete(Client, n.ID)
th.AssertEquals(t, n.Name, "sample_network")
th.AssertEquals(t, n.AdminStateUp, true)
networkID := n.ID
// List networks
pager := networks.List(Client, networks.ListOpts{Limit: 2})
err = pager.EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page ---")
networkList, err := networks.ExtractNetworks(page)
th.AssertNoErr(t, err)
for _, n := range networkList {
t.Logf("Network: ID [%s] Name [%s] Status [%s] Is shared? [%s]",
n.ID, n.Name, n.Status, strconv.FormatBool(n.Shared))
}
return true, nil
})
th.CheckNoErr(t, err)
// Get a network
if networkID == "" {
t.Fatalf("In order to retrieve a network, the NetworkID must be set")
}
n, err = networks.Get(Client, networkID).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, n.Status, "ACTIVE")
th.AssertDeepEquals(t, n.Subnets, []string{})
th.AssertEquals(t, n.Name, "sample_network")
th.AssertEquals(t, n.AdminStateUp, true)
th.AssertEquals(t, n.Shared, false)
th.AssertEquals(t, n.ID, networkID)
// Update network
n, err = networks.Update(Client, networkID, networks.UpdateOpts{Name: "new_network_name"}).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, n.Name, "new_network_name")
// Delete network
res := networks.Delete(Client, networkID)
th.AssertNoErr(t, res.Err)
}
func TestCreateMultipleNetworks(t *testing.T) {
//networks.CreateMany()
}

View File

@ -0,0 +1 @@
package v2

View File

@ -0,0 +1,117 @@
// +build acceptance networking
package v2
import (
"testing"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/openstack/networking/v2/ports"
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestPortCRUD(t *testing.T) {
Setup(t)
defer Teardown()
// Setup network
t.Log("Setting up network")
networkID, err := createNetwork()
th.AssertNoErr(t, err)
defer networks.Delete(Client, networkID)
// Setup subnet
t.Logf("Setting up subnet on network %s", networkID)
subnetID, err := createSubnet(networkID)
th.AssertNoErr(t, err)
defer subnets.Delete(Client, subnetID)
// Create port
t.Logf("Create port based on subnet %s", subnetID)
portID := createPort(t, networkID, subnetID)
// List ports
t.Logf("Listing all ports")
listPorts(t)
// Get port
if portID == "" {
t.Fatalf("In order to retrieve a port, the portID must be set")
}
p, err := ports.Get(Client, portID).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, p.ID, portID)
// Update port
p, err = ports.Update(Client, portID, ports.UpdateOpts{Name: "new_port_name"}).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, p.Name, "new_port_name")
// Delete port
res := ports.Delete(Client, portID)
th.AssertNoErr(t, res.Err)
}
func createPort(t *testing.T, networkID, subnetID string) string {
enable := false
opts := ports.CreateOpts{
NetworkID: networkID,
Name: "my_port",
AdminStateUp: &enable,
FixedIPs: []ports.IP{ports.IP{SubnetID: subnetID}},
}
p, err := ports.Create(Client, opts).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, p.NetworkID, networkID)
th.AssertEquals(t, p.Name, "my_port")
th.AssertEquals(t, p.AdminStateUp, false)
return p.ID
}
func listPorts(t *testing.T) {
count := 0
pager := ports.List(Client, ports.ListOpts{})
err := pager.EachPage(func(page pagination.Page) (bool, error) {
count++
t.Logf("--- Page ---")
portList, err := ports.ExtractPorts(page)
th.AssertNoErr(t, err)
for _, p := range portList {
t.Logf("Port: ID [%s] Name [%s] Status [%s] MAC addr [%s] Fixed IPs [%#v] Security groups [%#v]",
p.ID, p.Name, p.Status, p.MACAddress, p.FixedIPs, p.SecurityGroups)
}
return true, nil
})
th.CheckNoErr(t, err)
if count == 0 {
t.Logf("No pages were iterated over when listing ports")
}
}
func createNetwork() (string, error) {
res, err := networks.Create(Client, networks.CreateOpts{Name: "tmp_network", AdminStateUp: networks.Up}).Extract()
return res.ID, err
}
func createSubnet(networkID string) (string, error) {
s, err := subnets.Create(Client, subnets.CreateOpts{
NetworkID: networkID,
CIDR: "192.168.199.0/24",
IPVersion: subnets.IPv4,
Name: "my_subnet",
EnableDHCP: subnets.Down,
}).Extract()
return s.ID, err
}
func TestPortBatchCreate(t *testing.T) {
// todo
}

View File

@ -0,0 +1,86 @@
// +build acceptance networking
package v2
import (
"testing"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestList(t *testing.T) {
Setup(t)
defer Teardown()
pager := subnets.List(Client, subnets.ListOpts{Limit: 2})
err := pager.EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page ---")
subnetList, err := subnets.ExtractSubnets(page)
th.AssertNoErr(t, err)
for _, s := range subnetList {
t.Logf("Subnet: ID [%s] Name [%s] IP Version [%d] CIDR [%s] GatewayIP [%s]",
s.ID, s.Name, s.IPVersion, s.CIDR, s.GatewayIP)
}
return true, nil
})
th.CheckNoErr(t, err)
}
func TestCRUD(t *testing.T) {
Setup(t)
defer Teardown()
// Setup network
t.Log("Setting up network")
n, err := networks.Create(Client, networks.CreateOpts{Name: "tmp_network", AdminStateUp: networks.Up}).Extract()
th.AssertNoErr(t, err)
networkID := n.ID
defer networks.Delete(Client, networkID)
// Create subnet
t.Log("Create subnet")
enable := false
opts := subnets.CreateOpts{
NetworkID: networkID,
CIDR: "192.168.199.0/24",
IPVersion: subnets.IPv4,
Name: "my_subnet",
EnableDHCP: &enable,
}
s, err := subnets.Create(Client, opts).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, s.NetworkID, networkID)
th.AssertEquals(t, s.CIDR, "192.168.199.0/24")
th.AssertEquals(t, s.IPVersion, 4)
th.AssertEquals(t, s.Name, "my_subnet")
th.AssertEquals(t, s.EnableDHCP, false)
subnetID := s.ID
// Get subnet
t.Log("Getting subnet")
s, err = subnets.Get(Client, subnetID).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, s.ID, subnetID)
// Update subnet
t.Log("Update subnet")
s, err = subnets.Update(Client, subnetID, subnets.UpdateOpts{Name: "new_subnet_name"}).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, s.Name, "new_subnet_name")
// Delete subnet
t.Log("Delete subnet")
res := subnets.Delete(Client, subnetID)
th.AssertNoErr(t, res.Err)
}
func TestBatchCreate(t *testing.T) {
// todo
}

View File

@ -0,0 +1,44 @@
// +build acceptance
package v1
import (
"strings"
"testing"
"github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestAccounts(t *testing.T) {
// Create a provider client for making the HTTP requests.
// See common.go in this directory for more information.
client := newClient(t)
// Update an account's metadata.
updateres := accounts.Update(client, accounts.UpdateOpts{Metadata: metadata})
th.AssertNoErr(t, updateres.Err)
// Defer the deletion of the metadata set above.
defer func() {
tempMap := make(map[string]string)
for k := range metadata {
tempMap[k] = ""
}
updateres = accounts.Update(client, accounts.UpdateOpts{Metadata: tempMap})
th.AssertNoErr(t, updateres.Err)
}()
// Retrieve account metadata.
getres := accounts.Get(client, nil)
th.AssertNoErr(t, getres.Err)
// Extract the custom metadata from the 'Get' response.
am, err := getres.ExtractMetadata()
th.AssertNoErr(t, err)
for k := range metadata {
if am[k] != metadata[strings.Title(k)] {
t.Errorf("Expected custom metadata with key: %s", k)
return
}
}
}

View File

@ -0,0 +1,28 @@
// +build acceptance
package v1
import (
"os"
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
th "github.com/rackspace/gophercloud/testhelper"
)
var metadata = map[string]string{"gopher": "cloud"}
func newClient(t *testing.T) *gophercloud.ServiceClient {
ao, err := openstack.AuthOptionsFromEnv()
th.AssertNoErr(t, err)
client, err := openstack.AuthenticatedClient(ao)
th.AssertNoErr(t, err)
c, err := openstack.NewObjectStorageV1(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
th.AssertNoErr(t, err)
return c
}

View File

@ -0,0 +1,89 @@
// +build acceptance
package v1
import (
"strings"
"testing"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
// numContainers is the number of containers to create for testing.
var numContainers = 2
func TestContainers(t *testing.T) {
// Create a new client to execute the HTTP requests. See common.go for newClient body.
client := newClient(t)
// Create a slice of random container names.
cNames := make([]string, numContainers)
for i := 0; i < numContainers; i++ {
cNames[i] = tools.RandomString("gophercloud-test-container-", 8)
}
// Create numContainers containers.
for i := 0; i < len(cNames); i++ {
res := containers.Create(client, cNames[i], nil)
th.AssertNoErr(t, res.Err)
}
// Delete the numContainers containers after function completion.
defer func() {
for i := 0; i < len(cNames); i++ {
res := containers.Delete(client, cNames[i])
th.AssertNoErr(t, res.Err)
}
}()
// List the numContainer names that were just created. To just list those,
// the 'prefix' parameter is used.
err := containers.List(client, &containers.ListOpts{Full: true, Prefix: "gophercloud-test-container-"}).EachPage(func(page pagination.Page) (bool, error) {
containerList, err := containers.ExtractInfo(page)
th.AssertNoErr(t, err)
for _, n := range containerList {
t.Logf("Container: Name [%s] Count [%d] Bytes [%d]",
n.Name, n.Count, n.Bytes)
}
return true, nil
})
th.AssertNoErr(t, err)
// List the info for the numContainer containers that were created.
err = containers.List(client, &containers.ListOpts{Full: false, Prefix: "gophercloud-test-container-"}).EachPage(func(page pagination.Page) (bool, error) {
containerList, err := containers.ExtractNames(page)
th.AssertNoErr(t, err)
for _, n := range containerList {
t.Logf("Container: Name [%s]", n)
}
return true, nil
})
th.AssertNoErr(t, err)
// Update one of the numContainer container metadata.
updateres := containers.Update(client, cNames[0], &containers.UpdateOpts{Metadata: metadata})
th.AssertNoErr(t, updateres.Err)
// After the tests are done, delete the metadata that was set.
defer func() {
tempMap := make(map[string]string)
for k := range metadata {
tempMap[k] = ""
}
res := containers.Update(client, cNames[0], &containers.UpdateOpts{Metadata: tempMap})
th.AssertNoErr(t, res.Err)
}()
// Retrieve a container's metadata.
cm, err := containers.Get(client, cNames[0]).ExtractMetadata()
th.AssertNoErr(t, err)
for k := range metadata {
if cm[k] != metadata[strings.Title(k)] {
t.Errorf("Expected custom metadata with key: %s", k)
}
}
}

View File

@ -0,0 +1,119 @@
// +build acceptance
package v1
import (
"bytes"
"strings"
"testing"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
"github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects"
"github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
// numObjects is the number of objects to create for testing.
var numObjects = 2
func TestObjects(t *testing.T) {
// Create a provider client for executing the HTTP request.
// See common.go for more information.
client := newClient(t)
// Make a slice of length numObjects to hold the random object names.
oNames := make([]string, numObjects)
for i := 0; i < len(oNames); i++ {
oNames[i] = tools.RandomString("test-object-", 8)
}
// Create a container to hold the test objects.
cName := tools.RandomString("test-container-", 8)
header, err := containers.Create(client, cName, nil).ExtractHeader()
th.AssertNoErr(t, err)
t.Logf("Create object headers: %+v\n", header)
// Defer deletion of the container until after testing.
defer func() {
res := containers.Delete(client, cName)
th.AssertNoErr(t, res.Err)
}()
// Create a slice of buffers to hold the test object content.
oContents := make([]*bytes.Buffer, numObjects)
for i := 0; i < numObjects; i++ {
oContents[i] = bytes.NewBuffer([]byte(tools.RandomString("", 10)))
res := objects.Create(client, cName, oNames[i], oContents[i], nil)
th.AssertNoErr(t, res.Err)
}
// Delete the objects after testing.
defer func() {
for i := 0; i < numObjects; i++ {
res := objects.Delete(client, cName, oNames[i], nil)
th.AssertNoErr(t, res.Err)
}
}()
ons := make([]string, 0, len(oNames))
err = objects.List(client, cName, &objects.ListOpts{Full: false, Prefix: "test-object-"}).EachPage(func(page pagination.Page) (bool, error) {
names, err := objects.ExtractNames(page)
th.AssertNoErr(t, err)
ons = append(ons, names...)
return true, nil
})
th.AssertNoErr(t, err)
th.AssertEquals(t, len(ons), len(oNames))
ois := make([]objects.Object, 0, len(oNames))
err = objects.List(client, cName, &objects.ListOpts{Full: true, Prefix: "test-object-"}).EachPage(func(page pagination.Page) (bool, error) {
info, err := objects.ExtractInfo(page)
th.AssertNoErr(t, err)
ois = append(ois, info...)
return true, nil
})
th.AssertNoErr(t, err)
th.AssertEquals(t, len(ois), len(oNames))
// Copy the contents of one object to another.
copyres := objects.Copy(client, cName, oNames[0], &objects.CopyOpts{Destination: cName + "/" + oNames[1]})
th.AssertNoErr(t, copyres.Err)
// Download one of the objects that was created above.
o1Content, err := objects.Download(client, cName, oNames[0], nil).ExtractContent()
th.AssertNoErr(t, err)
// Download the another object that was create above.
o2Content, err := objects.Download(client, cName, oNames[1], nil).ExtractContent()
th.AssertNoErr(t, err)
// Compare the two object's contents to test that the copy worked.
th.AssertEquals(t, string(o2Content), string(o1Content))
// Update an object's metadata.
updateres := objects.Update(client, cName, oNames[0], &objects.UpdateOpts{Metadata: metadata})
th.AssertNoErr(t, updateres.Err)
// Delete the object's metadata after testing.
defer func() {
tempMap := make(map[string]string)
for k := range metadata {
tempMap[k] = ""
}
res := objects.Update(client, cName, oNames[0], &objects.UpdateOpts{Metadata: tempMap})
th.AssertNoErr(t, res.Err)
}()
// Retrieve an object's metadata.
om, err := objects.Get(client, cName, oNames[0], nil).ExtractMetadata()
th.AssertNoErr(t, err)
for k := range metadata {
if om[k] != metadata[strings.Title(k)] {
t.Errorf("Expected custom metadata with key: %s", k)
return
}
}
}

View File

@ -0,0 +1,4 @@
// +build acceptance
package openstack

View File

@ -0,0 +1,38 @@
// +build acceptance
package v1
import (
"os"
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/rackspace"
th "github.com/rackspace/gophercloud/testhelper"
)
func newClient() (*gophercloud.ServiceClient, error) {
opts, err := rackspace.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
opts = tools.OnlyRS(opts)
region := os.Getenv("RS_REGION")
provider, err := rackspace.AuthenticatedClient(opts)
if err != nil {
return nil, err
}
return rackspace.NewBlockStorageV1(provider, gophercloud.EndpointOpts{
Region: region,
})
}
func setup(t *testing.T) *gophercloud.ServiceClient {
client, err := newClient()
th.AssertNoErr(t, err)
return client
}

View File

@ -0,0 +1,82 @@
// +build acceptance blockstorage snapshots
package v1
import (
"testing"
"time"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/blockstorage/v1/snapshots"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestSnapshots(t *testing.T) {
client := setup(t)
volID := testVolumeCreate(t, client)
t.Log("Creating snapshots")
s := testSnapshotCreate(t, client, volID)
id := s.ID
t.Log("Listing snapshots")
testSnapshotList(t, client)
t.Logf("Getting snapshot %s", id)
testSnapshotGet(t, client, id)
t.Logf("Updating snapshot %s", id)
testSnapshotUpdate(t, client, id)
t.Logf("Deleting snapshot %s", id)
testSnapshotDelete(t, client, id)
s.WaitUntilDeleted(client, -1)
t.Logf("Deleting volume %s", volID)
testVolumeDelete(t, client, volID)
}
func testSnapshotCreate(t *testing.T, client *gophercloud.ServiceClient, volID string) *snapshots.Snapshot {
opts := snapshots.CreateOpts{VolumeID: volID, Name: "snapshot-001"}
s, err := snapshots.Create(client, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Created snapshot %s", s.ID)
t.Logf("Waiting for new snapshot to become available...")
start := time.Now().Second()
s.WaitUntilComplete(client, -1)
t.Logf("Snapshot completed after %ds", time.Now().Second()-start)
return s
}
func testSnapshotList(t *testing.T, client *gophercloud.ServiceClient) {
snapshots.List(client).EachPage(func(page pagination.Page) (bool, error) {
sList, err := snapshots.ExtractSnapshots(page)
th.AssertNoErr(t, err)
for _, s := range sList {
t.Logf("Snapshot: ID [%s] Name [%s] Volume ID [%s] Progress [%s] Created [%s]",
s.ID, s.Name, s.VolumeID, s.Progress, s.CreatedAt)
}
return true, nil
})
}
func testSnapshotGet(t *testing.T, client *gophercloud.ServiceClient, id string) {
_, err := snapshots.Get(client, id).Extract()
th.AssertNoErr(t, err)
}
func testSnapshotUpdate(t *testing.T, client *gophercloud.ServiceClient, id string) {
_, err := snapshots.Update(client, id, snapshots.UpdateOpts{Name: "new_name"}).Extract()
th.AssertNoErr(t, err)
}
func testSnapshotDelete(t *testing.T, client *gophercloud.ServiceClient, id string) {
res := snapshots.Delete(client, id)
th.AssertNoErr(t, res.Err)
t.Logf("Deleted snapshot %s", id)
}

View File

@ -0,0 +1,71 @@
// +build acceptance blockstorage volumes
package v1
import (
"testing"
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/blockstorage/v1/volumes"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestVolumes(t *testing.T) {
client := setup(t)
t.Logf("Listing volumes")
testVolumeList(t, client)
t.Logf("Creating volume")
volumeID := testVolumeCreate(t, client)
t.Logf("Getting volume %s", volumeID)
testVolumeGet(t, client, volumeID)
t.Logf("Updating volume %s", volumeID)
testVolumeUpdate(t, client, volumeID)
t.Logf("Deleting volume %s", volumeID)
testVolumeDelete(t, client, volumeID)
}
func testVolumeList(t *testing.T, client *gophercloud.ServiceClient) {
volumes.List(client).EachPage(func(page pagination.Page) (bool, error) {
vList, err := volumes.ExtractVolumes(page)
th.AssertNoErr(t, err)
for _, v := range vList {
t.Logf("Volume: ID [%s] Name [%s] Type [%s] Created [%s]", v.ID, v.Name,
v.VolumeType, v.CreatedAt)
}
return true, nil
})
}
func testVolumeCreate(t *testing.T, client *gophercloud.ServiceClient) string {
vol, err := volumes.Create(client, os.CreateOpts{Size: 75}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created volume: ID [%s] Size [%s]", vol.ID, vol.Size)
return vol.ID
}
func testVolumeGet(t *testing.T, client *gophercloud.ServiceClient, id string) {
vol, err := volumes.Get(client, id).Extract()
th.AssertNoErr(t, err)
t.Logf("Created volume: ID [%s] Size [%s]", vol.ID, vol.Size)
}
func testVolumeUpdate(t *testing.T, client *gophercloud.ServiceClient, id string) {
vol, err := volumes.Update(client, id, volumes.UpdateOpts{Name: "new_name"}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created volume: ID [%s] Name [%s]", vol.ID, vol.Name)
}
func testVolumeDelete(t *testing.T, client *gophercloud.ServiceClient, id string) {
res := volumes.Delete(client, id)
th.AssertNoErr(t, res.Err)
t.Logf("Deleted volume %s", id)
}

View File

@ -0,0 +1,46 @@
// +build acceptance blockstorage volumetypes
package v1
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/blockstorage/v1/volumetypes"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestAll(t *testing.T) {
client := setup(t)
t.Logf("Listing volume types")
id := testList(t, client)
t.Logf("Getting volume type %s", id)
testGet(t, client, id)
}
func testList(t *testing.T, client *gophercloud.ServiceClient) string {
var lastID string
volumetypes.List(client).EachPage(func(page pagination.Page) (bool, error) {
typeList, err := volumetypes.ExtractVolumeTypes(page)
th.AssertNoErr(t, err)
for _, vt := range typeList {
t.Logf("Volume type: ID [%s] Name [%s]", vt.ID, vt.Name)
lastID = vt.ID
}
return true, nil
})
return lastID
}
func testGet(t *testing.T, client *gophercloud.ServiceClient, id string) {
vt, err := volumetypes.Get(client, id).Extract()
th.AssertNoErr(t, err)
t.Logf("Volume: ID [%s] Name [%s]", vt.ID, vt.Name)
}

View File

@ -0,0 +1,28 @@
// +build acceptance
package rackspace
import (
"testing"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/rackspace"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestAuthenticatedClient(t *testing.T) {
// Obtain credentials from the environment.
ao, err := rackspace.AuthOptionsFromEnv()
th.AssertNoErr(t, err)
client, err := rackspace.AuthenticatedClient(tools.OnlyRS(ao))
if err != nil {
t.Fatalf("Unable to authenticate: %v", err)
}
if client.TokenID == "" {
t.Errorf("No token ID assigned to the client")
}
t.Logf("Client successfully acquired a token: %v", client.TokenID)
}

View File

@ -0,0 +1,46 @@
// +build acceptance
package v2
import (
"testing"
"github.com/rackspace/gophercloud/acceptance/tools"
osBFV "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
"github.com/rackspace/gophercloud/rackspace/compute/v2/bootfromvolume"
"github.com/rackspace/gophercloud/rackspace/compute/v2/servers"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestBootFromVolume(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
options, err := optionsFromEnv()
th.AssertNoErr(t, err)
name := tools.RandomString("Gophercloud-", 8)
t.Logf("Creating server [%s].", name)
bd := []osBFV.BlockDevice{
osBFV.BlockDevice{
UUID: options.imageID,
SourceType: osBFV.Image,
VolumeSize: 10,
},
}
server, err := bootfromvolume.Create(client, servers.CreateOpts{
Name: name,
FlavorRef: "performance1-1",
BlockDevice: bd,
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created server: %+v\n", server)
//defer deleteServer(t, client, server)
t.Logf("Deleting server [%s]...", name)
}

View File

@ -0,0 +1,60 @@
// +build acceptance
package v2
import (
"errors"
"os"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/rackspace"
)
func newClient() (*gophercloud.ServiceClient, error) {
// Obtain credentials from the environment.
options, err := rackspace.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
options = tools.OnlyRS(options)
region := os.Getenv("RS_REGION")
if options.Username == "" {
return nil, errors.New("Please provide a Rackspace username as RS_USERNAME.")
}
if options.APIKey == "" {
return nil, errors.New("Please provide a Rackspace API key as RS_API_KEY.")
}
if region == "" {
return nil, errors.New("Please provide a Rackspace region as RS_REGION.")
}
client, err := rackspace.AuthenticatedClient(options)
if err != nil {
return nil, err
}
return rackspace.NewComputeV2(client, gophercloud.EndpointOpts{
Region: region,
})
}
type serverOpts struct {
imageID string
flavorID string
}
func optionsFromEnv() (*serverOpts, error) {
options := &serverOpts{
imageID: os.Getenv("RS_IMAGE_ID"),
flavorID: os.Getenv("RS_FLAVOR_ID"),
}
if options.imageID == "" {
return nil, errors.New("Please provide a valid Rackspace image ID as RS_IMAGE_ID")
}
if options.flavorID == "" {
return nil, errors.New("Please provide a valid Rackspace flavor ID as RS_FLAVOR_ID")
}
return options, nil
}

View File

@ -0,0 +1,61 @@
// +build acceptance
package v2
import (
"testing"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/compute/v2/flavors"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestListFlavors(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
count := 0
err = flavors.ListDetail(client, nil).EachPage(func(page pagination.Page) (bool, error) {
count++
t.Logf("-- Page %0d --", count)
fs, err := flavors.ExtractFlavors(page)
th.AssertNoErr(t, err)
for i, flavor := range fs {
t.Logf("[%02d] id=[%s]", i, flavor.ID)
t.Logf(" name=[%s]", flavor.Name)
t.Logf(" disk=[%d]", flavor.Disk)
t.Logf(" RAM=[%d]", flavor.RAM)
t.Logf(" rxtx_factor=[%f]", flavor.RxTxFactor)
t.Logf(" swap=[%d]", flavor.Swap)
t.Logf(" VCPUs=[%d]", flavor.VCPUs)
}
return true, nil
})
th.AssertNoErr(t, err)
if count == 0 {
t.Errorf("No flavors listed!")
}
}
func TestGetFlavor(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
options, err := optionsFromEnv()
th.AssertNoErr(t, err)
flavor, err := flavors.Get(client, options.flavorID).Extract()
th.AssertNoErr(t, err)
t.Logf("Requested flavor:")
t.Logf(" id=[%s]", flavor.ID)
t.Logf(" name=[%s]", flavor.Name)
t.Logf(" disk=[%d]", flavor.Disk)
t.Logf(" RAM=[%d]", flavor.RAM)
t.Logf(" rxtx_factor=[%f]", flavor.RxTxFactor)
t.Logf(" swap=[%d]", flavor.Swap)
t.Logf(" VCPUs=[%d]", flavor.VCPUs)
}

View File

@ -0,0 +1,63 @@
// +build acceptance
package v2
import (
"testing"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/compute/v2/images"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestListImages(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
count := 0
err = images.ListDetail(client, nil).EachPage(func(page pagination.Page) (bool, error) {
count++
t.Logf("-- Page %02d --", count)
is, err := images.ExtractImages(page)
th.AssertNoErr(t, err)
for i, image := range is {
t.Logf("[%02d] id=[%s]", i, image.ID)
t.Logf(" name=[%s]", image.Name)
t.Logf(" created=[%s]", image.Created)
t.Logf(" updated=[%s]", image.Updated)
t.Logf(" min disk=[%d]", image.MinDisk)
t.Logf(" min RAM=[%d]", image.MinRAM)
t.Logf(" progress=[%d]", image.Progress)
t.Logf(" status=[%s]", image.Status)
}
return true, nil
})
th.AssertNoErr(t, err)
if count < 1 {
t.Errorf("Expected at least one page of images.")
}
}
func TestGetImage(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
options, err := optionsFromEnv()
th.AssertNoErr(t, err)
image, err := images.Get(client, options.imageID).Extract()
th.AssertNoErr(t, err)
t.Logf("Requested image:")
t.Logf(" id=[%s]", image.ID)
t.Logf(" name=[%s]", image.Name)
t.Logf(" created=[%s]", image.Created)
t.Logf(" updated=[%s]", image.Updated)
t.Logf(" min disk=[%d]", image.MinDisk)
t.Logf(" min RAM=[%d]", image.MinRAM)
t.Logf(" progress=[%d]", image.Progress)
t.Logf(" status=[%s]", image.Status)
}

View File

@ -0,0 +1,87 @@
// +build acceptance rackspace
package v2
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
os "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/compute/v2/keypairs"
th "github.com/rackspace/gophercloud/testhelper"
)
func deleteKeyPair(t *testing.T, client *gophercloud.ServiceClient, name string) {
err := keypairs.Delete(client, name).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Successfully deleted key [%s].", name)
}
func TestCreateKeyPair(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
name := tools.RandomString("createdkey-", 8)
k, err := keypairs.Create(client, os.CreateOpts{Name: name}).Extract()
th.AssertNoErr(t, err)
defer deleteKeyPair(t, client, name)
t.Logf("Created a new keypair:")
t.Logf(" name=[%s]", k.Name)
t.Logf(" fingerprint=[%s]", k.Fingerprint)
t.Logf(" publickey=[%s]", tools.Elide(k.PublicKey))
t.Logf(" privatekey=[%s]", tools.Elide(k.PrivateKey))
t.Logf(" userid=[%s]", k.UserID)
}
func TestImportKeyPair(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
name := tools.RandomString("importedkey-", 8)
pubkey := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDlIQ3r+zd97kb9Hzmujd3V6pbO53eb3Go4q2E8iqVGWQfZTrFdL9KACJnqJIm9HmncfRkUTxE37hqeGCCv8uD+ZPmPiZG2E60OX1mGDjbbzAyReRwYWXgXHopggZTLak5k4mwZYaxwaufbVBDRn847e01lZnaXaszEToLM37NLw+uz29sl3TwYy2R0RGHPwPc160aWmdLjSyd1Nd4c9pvvOP/EoEuBjIC6NJJwg2Rvg9sjjx9jYj0QUgc8CqKLN25oMZ69kNJzlFylKRUoeeVr89txlR59yehJWk6Uw6lYFTdJmcmQOFVAJ12RMmS1hLWCM8UzAgtw+EDa0eqBxBDl smash@winter"
k, err := keypairs.Create(client, os.CreateOpts{
Name: name,
PublicKey: pubkey,
}).Extract()
th.AssertNoErr(t, err)
defer deleteKeyPair(t, client, name)
th.CheckEquals(t, pubkey, k.PublicKey)
th.CheckEquals(t, "", k.PrivateKey)
t.Logf("Imported an existing keypair:")
t.Logf(" name=[%s]", k.Name)
t.Logf(" fingerprint=[%s]", k.Fingerprint)
t.Logf(" publickey=[%s]", tools.Elide(k.PublicKey))
t.Logf(" privatekey=[%s]", tools.Elide(k.PrivateKey))
t.Logf(" userid=[%s]", k.UserID)
}
func TestListKeyPairs(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
count := 0
err = keypairs.List(client).EachPage(func(page pagination.Page) (bool, error) {
count++
t.Logf("--- %02d ---", count)
ks, err := keypairs.ExtractKeyPairs(page)
th.AssertNoErr(t, err)
for i, keypair := range ks {
t.Logf("[%02d] name=[%s]", i, keypair.Name)
t.Logf(" fingerprint=[%s]", keypair.Fingerprint)
t.Logf(" publickey=[%s]", tools.Elide(keypair.PublicKey))
t.Logf(" privatekey=[%s]", tools.Elide(keypair.PrivateKey))
t.Logf(" userid=[%s]", keypair.UserID)
}
return true, nil
})
th.AssertNoErr(t, err)
}

View File

@ -0,0 +1,53 @@
// +build acceptance rackspace
package v2
import (
"testing"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/compute/v2/networks"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestNetworks(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
// Create a network
n, err := networks.Create(client, networks.CreateOpts{Label: "sample_network", CIDR: "172.20.0.0/24"}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created network: %+v\n", n)
defer networks.Delete(client, n.ID)
th.AssertEquals(t, n.Label, "sample_network")
th.AssertEquals(t, n.CIDR, "172.20.0.0/24")
networkID := n.ID
// List networks
pager := networks.List(client)
err = pager.EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page ---")
networkList, err := networks.ExtractNetworks(page)
th.AssertNoErr(t, err)
for _, n := range networkList {
t.Logf("Network: ID [%s] Label [%s] CIDR [%s]",
n.ID, n.Label, n.CIDR)
}
return true, nil
})
th.CheckNoErr(t, err)
// Get a network
if networkID == "" {
t.Fatalf("In order to retrieve a network, the NetworkID must be set")
}
n, err = networks.Get(client, networkID).Extract()
t.Logf("Retrieved Network: %+v\n", n)
th.AssertNoErr(t, err)
th.AssertEquals(t, n.CIDR, "172.20.0.0/24")
th.AssertEquals(t, n.Label, "sample_network")
th.AssertEquals(t, n.ID, networkID)
}

View File

@ -0,0 +1 @@
package v2

View File

@ -0,0 +1,204 @@
// +build acceptance
package v2
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/diskconfig"
oskey "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
os "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/compute/v2/keypairs"
"github.com/rackspace/gophercloud/rackspace/compute/v2/servers"
th "github.com/rackspace/gophercloud/testhelper"
)
func createServerKeyPair(t *testing.T, client *gophercloud.ServiceClient) *oskey.KeyPair {
name := tools.RandomString("importedkey-", 8)
pubkey := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDlIQ3r+zd97kb9Hzmujd3V6pbO53eb3Go4q2E8iqVGWQfZTrFdL9KACJnqJIm9HmncfRkUTxE37hqeGCCv8uD+ZPmPiZG2E60OX1mGDjbbzAyReRwYWXgXHopggZTLak5k4mwZYaxwaufbVBDRn847e01lZnaXaszEToLM37NLw+uz29sl3TwYy2R0RGHPwPc160aWmdLjSyd1Nd4c9pvvOP/EoEuBjIC6NJJwg2Rvg9sjjx9jYj0QUgc8CqKLN25oMZ69kNJzlFylKRUoeeVr89txlR59yehJWk6Uw6lYFTdJmcmQOFVAJ12RMmS1hLWCM8UzAgtw+EDa0eqBxBDl smash@winter"
k, err := keypairs.Create(client, oskey.CreateOpts{
Name: name,
PublicKey: pubkey,
}).Extract()
th.AssertNoErr(t, err)
return k
}
func createServer(t *testing.T, client *gophercloud.ServiceClient, keyName string) *os.Server {
if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}
options, err := optionsFromEnv()
th.AssertNoErr(t, err)
name := tools.RandomString("Gophercloud-", 8)
pwd := tools.MakeNewPassword("")
opts := &servers.CreateOpts{
Name: name,
ImageRef: options.imageID,
FlavorRef: options.flavorID,
DiskConfig: diskconfig.Manual,
AdminPass: pwd,
}
if keyName != "" {
opts.KeyPair = keyName
}
t.Logf("Creating server [%s].", name)
s, err := servers.Create(client, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Creating server.")
err = servers.WaitForStatus(client, s.ID, "ACTIVE", 300)
th.AssertNoErr(t, err)
t.Logf("Server created successfully.")
th.CheckEquals(t, pwd, s.AdminPass)
return s
}
func logServer(t *testing.T, server *os.Server, index int) {
if index == -1 {
t.Logf(" id=[%s]", server.ID)
} else {
t.Logf("[%02d] id=[%s]", index, server.ID)
}
t.Logf(" name=[%s]", server.Name)
t.Logf(" tenant ID=[%s]", server.TenantID)
t.Logf(" user ID=[%s]", server.UserID)
t.Logf(" updated=[%s]", server.Updated)
t.Logf(" created=[%s]", server.Created)
t.Logf(" host ID=[%s]", server.HostID)
t.Logf(" access IPv4=[%s]", server.AccessIPv4)
t.Logf(" access IPv6=[%s]", server.AccessIPv6)
t.Logf(" image=[%v]", server.Image)
t.Logf(" flavor=[%v]", server.Flavor)
t.Logf(" addresses=[%v]", server.Addresses)
t.Logf(" metadata=[%v]", server.Metadata)
t.Logf(" links=[%v]", server.Links)
t.Logf(" keyname=[%s]", server.KeyName)
t.Logf(" admin password=[%s]", server.AdminPass)
t.Logf(" status=[%s]", server.Status)
t.Logf(" progress=[%d]", server.Progress)
}
func getServer(t *testing.T, client *gophercloud.ServiceClient, server *os.Server) {
t.Logf("> servers.Get")
details, err := servers.Get(client, server.ID).Extract()
th.AssertNoErr(t, err)
logServer(t, details, -1)
}
func listServers(t *testing.T, client *gophercloud.ServiceClient) {
t.Logf("> servers.List")
count := 0
err := servers.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
count++
t.Logf("--- Page %02d ---", count)
s, err := servers.ExtractServers(page)
th.AssertNoErr(t, err)
for index, server := range s {
logServer(t, &server, index)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func changeAdminPassword(t *testing.T, client *gophercloud.ServiceClient, server *os.Server) {
t.Logf("> servers.ChangeAdminPassword")
original := server.AdminPass
t.Logf("Changing server password.")
err := servers.ChangeAdminPassword(client, server.ID, tools.MakeNewPassword(original)).ExtractErr()
th.AssertNoErr(t, err)
err = servers.WaitForStatus(client, server.ID, "ACTIVE", 300)
th.AssertNoErr(t, err)
t.Logf("Password changed successfully.")
}
func rebootServer(t *testing.T, client *gophercloud.ServiceClient, server *os.Server) {
t.Logf("> servers.Reboot")
err := servers.Reboot(client, server.ID, os.HardReboot).ExtractErr()
th.AssertNoErr(t, err)
err = servers.WaitForStatus(client, server.ID, "ACTIVE", 300)
th.AssertNoErr(t, err)
t.Logf("Server successfully rebooted.")
}
func rebuildServer(t *testing.T, client *gophercloud.ServiceClient, server *os.Server) {
t.Logf("> servers.Rebuild")
options, err := optionsFromEnv()
th.AssertNoErr(t, err)
opts := servers.RebuildOpts{
Name: tools.RandomString("RenamedGopher", 16),
AdminPass: tools.MakeNewPassword(server.AdminPass),
ImageID: options.imageID,
DiskConfig: diskconfig.Manual,
}
after, err := servers.Rebuild(client, server.ID, opts).Extract()
th.AssertNoErr(t, err)
th.CheckEquals(t, after.ID, server.ID)
err = servers.WaitForStatus(client, after.ID, "ACTIVE", 300)
th.AssertNoErr(t, err)
t.Logf("Server successfully rebuilt.")
logServer(t, after, -1)
}
func deleteServer(t *testing.T, client *gophercloud.ServiceClient, server *os.Server) {
t.Logf("> servers.Delete")
res := servers.Delete(client, server.ID)
th.AssertNoErr(t, res.Err)
t.Logf("Server deleted successfully.")
}
func deleteServerKeyPair(t *testing.T, client *gophercloud.ServiceClient, k *oskey.KeyPair) {
t.Logf("> keypairs.Delete")
err := keypairs.Delete(client, k.Name).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Keypair deleted successfully.")
}
func TestServerOperations(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
kp := createServerKeyPair(t, client)
defer deleteServerKeyPair(t, client, kp)
server := createServer(t, client, kp.Name)
defer deleteServer(t, client, server)
getServer(t, client, server)
listServers(t, client)
changeAdminPassword(t, client, server)
rebootServer(t, client, server)
rebuildServer(t, client, server)
}

View File

@ -0,0 +1,53 @@
// +build acceptance rackspace
package v2
import (
"testing"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/compute/v2/networks"
"github.com/rackspace/gophercloud/rackspace/compute/v2/virtualinterfaces"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestVirtualInterfaces(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)
// Create a server
server := createServer(t, client, "")
t.Logf("Created Server: %v\n", server)
defer deleteServer(t, client, server)
serverID := server.ID
// Create a network
n, err := networks.Create(client, networks.CreateOpts{Label: "sample_network", CIDR: "172.20.0.0/24"}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created Network: %v\n", n)
defer networks.Delete(client, n.ID)
networkID := n.ID
// Create a virtual interface
vi, err := virtualinterfaces.Create(client, serverID, networkID).Extract()
th.AssertNoErr(t, err)
t.Logf("Created virtual interface: %+v\n", vi)
defer virtualinterfaces.Delete(client, serverID, vi.ID)
// List virtual interfaces
pager := virtualinterfaces.List(client, serverID)
err = pager.EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page ---")
virtualinterfacesList, err := virtualinterfaces.ExtractVirtualInterfaces(page)
th.AssertNoErr(t, err)
for _, vi := range virtualinterfacesList {
t.Logf("Virtual Interface: ID [%s] MAC Address [%s] IP Addresses [%v]",
vi.ID, vi.MACAddress, vi.IPAddresses)
}
return true, nil
})
th.CheckNoErr(t, err)
}

View File

@ -0,0 +1,54 @@
// +build acceptance
package v2
import (
"testing"
"github.com/rackspace/gophercloud/pagination"
extensions2 "github.com/rackspace/gophercloud/rackspace/identity/v2/extensions"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestExtensions(t *testing.T) {
service := authenticatedClient(t)
t.Logf("Extensions available on this identity endpoint:")
count := 0
var chosen string
err := extensions2.List(service).EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page %02d ---", count)
extensions, err := extensions2.ExtractExtensions(page)
th.AssertNoErr(t, err)
for i, ext := range extensions {
if chosen == "" {
chosen = ext.Alias
}
t.Logf("[%02d] name=[%s] namespace=[%s]", i, ext.Name, ext.Namespace)
t.Logf(" alias=[%s] updated=[%s]", ext.Alias, ext.Updated)
t.Logf(" description=[%s]", ext.Description)
}
count++
return true, nil
})
th.AssertNoErr(t, err)
if chosen == "" {
t.Logf("No extensions found.")
return
}
ext, err := extensions2.Get(service, chosen).Extract()
th.AssertNoErr(t, err)
t.Logf("Detail for extension [%s]:", chosen)
t.Logf(" name=[%s]", ext.Name)
t.Logf(" namespace=[%s]", ext.Namespace)
t.Logf(" alias=[%s]", ext.Alias)
t.Logf(" updated=[%s]", ext.Updated)
t.Logf(" description=[%s]", ext.Description)
}

View File

@ -0,0 +1,50 @@
// +build acceptance
package v2
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/rackspace"
th "github.com/rackspace/gophercloud/testhelper"
)
func rackspaceAuthOptions(t *testing.T) gophercloud.AuthOptions {
// Obtain credentials from the environment.
options, err := rackspace.AuthOptionsFromEnv()
th.AssertNoErr(t, err)
options = tools.OnlyRS(options)
if options.Username == "" {
t.Fatal("Please provide a Rackspace username as RS_USERNAME.")
}
if options.APIKey == "" {
t.Fatal("Please provide a Rackspace API key as RS_API_KEY.")
}
return options
}
func createClient(t *testing.T, auth bool) *gophercloud.ServiceClient {
ao := rackspaceAuthOptions(t)
provider, err := rackspace.NewClient(ao.IdentityEndpoint)
th.AssertNoErr(t, err)
if auth {
err = rackspace.Authenticate(provider, ao)
th.AssertNoErr(t, err)
}
return rackspace.NewIdentityV2(provider)
}
func unauthenticatedClient(t *testing.T) *gophercloud.ServiceClient {
return createClient(t, false)
}
func authenticatedClient(t *testing.T) *gophercloud.ServiceClient {
return createClient(t, true)
}

View File

@ -0,0 +1 @@
package v2

View File

@ -0,0 +1,59 @@
// +build acceptance identity roles
package v2
import (
"testing"
"github.com/rackspace/gophercloud"
os "github.com/rackspace/gophercloud/openstack/identity/v2/extensions/admin/roles"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/identity/v2/roles"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestRoles(t *testing.T) {
client := authenticatedClient(t)
userID := createUser(t, client)
roleID := listRoles(t, client)
addUserRole(t, client, userID, roleID)
deleteUserRole(t, client, userID, roleID)
deleteUser(t, client, userID)
}
func listRoles(t *testing.T, client *gophercloud.ServiceClient) string {
var roleID string
err := roles.List(client).EachPage(func(page pagination.Page) (bool, error) {
roleList, err := os.ExtractRoles(page)
th.AssertNoErr(t, err)
for _, role := range roleList {
t.Logf("Listing role: ID [%s] Name [%s]", role.ID, role.Name)
roleID = role.ID
}
return true, nil
})
th.AssertNoErr(t, err)
return roleID
}
func addUserRole(t *testing.T, client *gophercloud.ServiceClient, userID, roleID string) {
err := roles.AddUserRole(client, userID, roleID).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Added role %s to user %s", roleID, userID)
}
func deleteUserRole(t *testing.T, client *gophercloud.ServiceClient, userID, roleID string) {
err := roles.DeleteUserRole(client, userID, roleID).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Removed role %s from user %s", roleID, userID)
}

View File

@ -0,0 +1,37 @@
// +build acceptance
package v2
import (
"testing"
"github.com/rackspace/gophercloud/pagination"
rstenants "github.com/rackspace/gophercloud/rackspace/identity/v2/tenants"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestTenants(t *testing.T) {
service := authenticatedClient(t)
t.Logf("Tenants available to the currently issued token:")
count := 0
err := rstenants.List(service, nil).EachPage(func(page pagination.Page) (bool, error) {
t.Logf("--- Page %02d ---", count)
tenants, err := rstenants.ExtractTenants(page)
th.AssertNoErr(t, err)
for i, tenant := range tenants {
t.Logf("[%02d] id=[%s]", i, tenant.ID)
t.Logf(" name=[%s] enabled=[%v]", i, tenant.Name, tenant.Enabled)
t.Logf(" description=[%s]", tenant.Description)
}
count++
return true, nil
})
th.AssertNoErr(t, err)
if count == 0 {
t.Errorf("No tenants listed for your current token.")
}
}

View File

@ -0,0 +1,93 @@
// +build acceptance identity
package v2
import (
"strconv"
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
os "github.com/rackspace/gophercloud/openstack/identity/v2/users"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/identity/v2/users"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestUsers(t *testing.T) {
client := authenticatedClient(t)
userID := createUser(t, client)
listUsers(t, client)
getUser(t, client, userID)
updateUser(t, client, userID)
resetApiKey(t, client, userID)
deleteUser(t, client, userID)
}
func createUser(t *testing.T, client *gophercloud.ServiceClient) string {
t.Log("Creating user")
opts := users.CreateOpts{
Username: tools.RandomString("user_", 5),
Enabled: os.Disabled,
Email: "new_user@foo.com",
}
user, err := users.Create(client, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Created user %s", user.ID)
return user.ID
}
func listUsers(t *testing.T, client *gophercloud.ServiceClient) {
err := users.List(client).EachPage(func(page pagination.Page) (bool, error) {
userList, err := os.ExtractUsers(page)
th.AssertNoErr(t, err)
for _, user := range userList {
t.Logf("Listing user: ID [%s] Username [%s] Email [%s] Enabled? [%s]",
user.ID, user.Username, user.Email, strconv.FormatBool(user.Enabled))
}
return true, nil
})
th.AssertNoErr(t, err)
}
func getUser(t *testing.T, client *gophercloud.ServiceClient, userID string) {
_, err := users.Get(client, userID).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting user %s", userID)
}
func updateUser(t *testing.T, client *gophercloud.ServiceClient, userID string) {
opts := users.UpdateOpts{Username: tools.RandomString("new_name", 5), Email: "new@foo.com"}
user, err := users.Update(client, userID, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Updated user %s: Username [%s] Email [%s]", userID, user.Username, user.Email)
}
func deleteUser(t *testing.T, client *gophercloud.ServiceClient, userID string) {
res := users.Delete(client, userID)
th.AssertNoErr(t, res.Err)
t.Logf("Deleted user %s", userID)
}
func resetApiKey(t *testing.T, client *gophercloud.ServiceClient, userID string) {
key, err := users.ResetAPIKey(client, userID).Extract()
th.AssertNoErr(t, err)
if key.APIKey == "" {
t.Fatal("failed to reset API key for user")
}
t.Logf("Reset API key for user %s to %s", key.Username, key.APIKey)
}

View File

@ -0,0 +1,94 @@
// +build acceptance lbs
package v1
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/lb/v1/acl"
"github.com/rackspace/gophercloud/rackspace/lb/v1/lbs"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestACL(t *testing.T) {
client := setup(t)
ids := createLB(t, client, 1)
lbID := ids[0]
createACL(t, client, lbID)
waitForLB(client, lbID, lbs.ACTIVE)
networkIDs := showACL(t, client, lbID)
deleteNetworkItem(t, client, lbID, networkIDs[0])
waitForLB(client, lbID, lbs.ACTIVE)
bulkDeleteACL(t, client, lbID, networkIDs[1:2])
waitForLB(client, lbID, lbs.ACTIVE)
deleteACL(t, client, lbID)
waitForLB(client, lbID, lbs.ACTIVE)
deleteLB(t, client, lbID)
}
func createACL(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
opts := acl.CreateOpts{
acl.CreateOpt{Address: "206.160.163.21", Type: acl.DENY},
acl.CreateOpt{Address: "206.160.165.11", Type: acl.DENY},
acl.CreateOpt{Address: "206.160.165.12", Type: acl.DENY},
acl.CreateOpt{Address: "206.160.165.13", Type: acl.ALLOW},
}
err := acl.Create(client, lbID, opts).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Created ACL items for LB %d", lbID)
}
func showACL(t *testing.T, client *gophercloud.ServiceClient, lbID int) []int {
ids := []int{}
err := acl.List(client, lbID).EachPage(func(page pagination.Page) (bool, error) {
accessList, err := acl.ExtractAccessList(page)
th.AssertNoErr(t, err)
for _, i := range accessList {
t.Logf("Listing network item: ID [%s] Address [%s] Type [%s]", i.ID, i.Address, i.Type)
ids = append(ids, i.ID)
}
return true, nil
})
th.AssertNoErr(t, err)
return ids
}
func deleteNetworkItem(t *testing.T, client *gophercloud.ServiceClient, lbID, itemID int) {
err := acl.Delete(client, lbID, itemID).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Deleted network item %d", itemID)
}
func bulkDeleteACL(t *testing.T, client *gophercloud.ServiceClient, lbID int, items []int) {
err := acl.BulkDelete(client, lbID, items).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Deleted network items %s", intsToStr(items))
}
func deleteACL(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
err := acl.DeleteAll(client, lbID).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Deleted ACL from LB %d", lbID)
}

View File

@ -0,0 +1,62 @@
// +build acceptance lbs
package v1
import (
"os"
"strconv"
"strings"
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/rackspace"
th "github.com/rackspace/gophercloud/testhelper"
)
func newProvider() (*gophercloud.ProviderClient, error) {
opts, err := rackspace.AuthOptionsFromEnv()
if err != nil {
return nil, err
}
opts = tools.OnlyRS(opts)
return rackspace.AuthenticatedClient(opts)
}
func newClient() (*gophercloud.ServiceClient, error) {
provider, err := newProvider()
if err != nil {
return nil, err
}
return rackspace.NewLBV1(provider, gophercloud.EndpointOpts{
Region: os.Getenv("RS_REGION"),
})
}
func newComputeClient() (*gophercloud.ServiceClient, error) {
provider, err := newProvider()
if err != nil {
return nil, err
}
return rackspace.NewComputeV2(provider, gophercloud.EndpointOpts{
Region: os.Getenv("RS_REGION"),
})
}
func setup(t *testing.T) *gophercloud.ServiceClient {
client, err := newClient()
th.AssertNoErr(t, err)
return client
}
func intsToStr(ids []int) string {
strIDs := []string{}
for _, id := range ids {
strIDs = append(strIDs, strconv.Itoa(id))
}
return strings.Join(strIDs, ", ")
}

View File

@ -0,0 +1,214 @@
// +build acceptance lbs
package v1
import (
"strconv"
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/lb/v1/lbs"
"github.com/rackspace/gophercloud/rackspace/lb/v1/vips"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestLBs(t *testing.T) {
client := setup(t)
ids := createLB(t, client, 3)
id := ids[0]
listLBProtocols(t, client)
listLBAlgorithms(t, client)
listLBs(t, client)
getLB(t, client, id)
checkLBLogging(t, client, id)
checkErrorPage(t, client, id)
getStats(t, client, id)
updateLB(t, client, id)
deleteLB(t, client, id)
batchDeleteLBs(t, client, ids[1:])
}
func createLB(t *testing.T, client *gophercloud.ServiceClient, count int) []int {
ids := []int{}
for i := 0; i < count; i++ {
opts := lbs.CreateOpts{
Name: tools.RandomString("test_", 5),
Port: 80,
Protocol: "HTTP",
VIPs: []vips.VIP{
vips.VIP{Type: vips.PUBLIC},
},
}
lb, err := lbs.Create(client, opts).Extract()
th.AssertNoErr(t, err)
t.Logf("Created LB %d - waiting for it to build...", lb.ID)
waitForLB(client, lb.ID, lbs.ACTIVE)
t.Logf("LB %d has reached ACTIVE state", lb.ID)
ids = append(ids, lb.ID)
}
return ids
}
func waitForLB(client *gophercloud.ServiceClient, id int, state lbs.Status) {
gophercloud.WaitFor(60, func() (bool, error) {
lb, err := lbs.Get(client, id).Extract()
if err != nil {
return false, err
}
if lb.Status != state {
return false, nil
}
return true, nil
})
}
func listLBProtocols(t *testing.T, client *gophercloud.ServiceClient) {
err := lbs.ListProtocols(client).EachPage(func(page pagination.Page) (bool, error) {
pList, err := lbs.ExtractProtocols(page)
th.AssertNoErr(t, err)
for _, p := range pList {
t.Logf("Listing protocol: Name [%s]", p.Name)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func listLBAlgorithms(t *testing.T, client *gophercloud.ServiceClient) {
err := lbs.ListAlgorithms(client).EachPage(func(page pagination.Page) (bool, error) {
aList, err := lbs.ExtractAlgorithms(page)
th.AssertNoErr(t, err)
for _, a := range aList {
t.Logf("Listing algorithm: Name [%s]", a.Name)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func listLBs(t *testing.T, client *gophercloud.ServiceClient) {
err := lbs.List(client, lbs.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
lbList, err := lbs.ExtractLBs(page)
th.AssertNoErr(t, err)
for _, lb := range lbList {
t.Logf("Listing LB: ID [%d] Name [%s] Protocol [%s] Status [%s] Node count [%d] Port [%d]",
lb.ID, lb.Name, lb.Protocol, lb.Status, lb.NodeCount, lb.Port)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func getLB(t *testing.T, client *gophercloud.ServiceClient, id int) {
lb, err := lbs.Get(client, id).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting LB %d: Created [%s] VIPs [%#v] Logging [%#v] Persistence [%#v] SourceAddrs [%#v]",
lb.ID, lb.Created, lb.VIPs, lb.ConnectionLogging, lb.SessionPersistence, lb.SourceAddrs)
}
func updateLB(t *testing.T, client *gophercloud.ServiceClient, id int) {
opts := lbs.UpdateOpts{
Name: tools.RandomString("new_", 5),
Protocol: "TCP",
HalfClosed: gophercloud.Enabled,
Algorithm: "RANDOM",
Port: 8080,
Timeout: 100,
HTTPSRedirect: gophercloud.Disabled,
}
err := lbs.Update(client, id, opts).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Updating LB %d - waiting for it to finish", id)
waitForLB(client, id, lbs.ACTIVE)
t.Logf("LB %d has reached ACTIVE state", id)
}
func deleteLB(t *testing.T, client *gophercloud.ServiceClient, id int) {
err := lbs.Delete(client, id).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Deleted LB %d", id)
}
func batchDeleteLBs(t *testing.T, client *gophercloud.ServiceClient, ids []int) {
err := lbs.BulkDelete(client, ids).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Deleted LB %s", intsToStr(ids))
}
func checkLBLogging(t *testing.T, client *gophercloud.ServiceClient, id int) {
err := lbs.EnableLogging(client, id).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Enabled logging for LB %d", id)
res, err := lbs.IsLoggingEnabled(client, id)
th.AssertNoErr(t, err)
t.Logf("LB %d log enabled? %s", id, strconv.FormatBool(res))
waitForLB(client, id, lbs.ACTIVE)
err = lbs.DisableLogging(client, id).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Disabled logging for LB %d", id)
}
func checkErrorPage(t *testing.T, client *gophercloud.ServiceClient, id int) {
content, err := lbs.SetErrorPage(client, id, "<html>New content!</html>").Extract()
t.Logf("Set error page for LB %d", id)
content, err = lbs.GetErrorPage(client, id).Extract()
th.AssertNoErr(t, err)
t.Logf("Error page for LB %d: %s", id, content)
err = lbs.DeleteErrorPage(client, id).ExtractErr()
t.Logf("Deleted error page for LB %d", id)
}
func getStats(t *testing.T, client *gophercloud.ServiceClient, id int) {
waitForLB(client, id, lbs.ACTIVE)
stats, err := lbs.GetStats(client, id).Extract()
th.AssertNoErr(t, err)
t.Logf("Stats for LB %d: %#v", id, stats)
}
func checkCaching(t *testing.T, client *gophercloud.ServiceClient, id int) {
err := lbs.EnableCaching(client, id).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Enabled caching for LB %d", id)
res, err := lbs.IsContentCached(client, id)
th.AssertNoErr(t, err)
t.Logf("Is caching enabled for LB? %s", strconv.FormatBool(res))
err = lbs.DisableCaching(client, id).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Disabled caching for LB %d", id)
}

View File

@ -0,0 +1,60 @@
// +build acceptance lbs
package v1
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/rackspace/lb/v1/lbs"
"github.com/rackspace/gophercloud/rackspace/lb/v1/monitors"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestMonitors(t *testing.T) {
client := setup(t)
ids := createLB(t, client, 1)
lbID := ids[0]
getMonitor(t, client, lbID)
updateMonitor(t, client, lbID)
deleteMonitor(t, client, lbID)
deleteLB(t, client, lbID)
}
func getMonitor(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
hm, err := monitors.Get(client, lbID).Extract()
th.AssertNoErr(t, err)
t.Logf("Health monitor for LB %d: Type [%s] Delay [%d] Timeout [%d] AttemptLimit [%d]",
lbID, hm.Type, hm.Delay, hm.Timeout, hm.AttemptLimit)
}
func updateMonitor(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
opts := monitors.UpdateHTTPMonitorOpts{
AttemptLimit: 3,
Delay: 10,
Timeout: 10,
BodyRegex: "hello is it me you're looking for",
Path: "/foo",
StatusRegex: "200",
Type: monitors.HTTP,
}
err := monitors.Update(client, lbID, opts).ExtractErr()
th.AssertNoErr(t, err)
waitForLB(client, lbID, lbs.ACTIVE)
t.Logf("Updated monitor for LB %d", lbID)
}
func deleteMonitor(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
err := monitors.Delete(client, lbID).ExtractErr()
th.AssertNoErr(t, err)
waitForLB(client, lbID, lbs.ACTIVE)
t.Logf("Deleted monitor for LB %d", lbID)
}

View File

@ -0,0 +1,175 @@
// +build acceptance lbs
package v1
import (
"os"
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/diskconfig"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/compute/v2/servers"
"github.com/rackspace/gophercloud/rackspace/lb/v1/lbs"
"github.com/rackspace/gophercloud/rackspace/lb/v1/nodes"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestNodes(t *testing.T) {
client := setup(t)
serverIP := findServer(t)
ids := createLB(t, client, 1)
lbID := ids[0]
nodeID := addNodes(t, client, lbID, serverIP)
listNodes(t, client, lbID)
getNode(t, client, lbID, nodeID)
updateNode(t, client, lbID, nodeID)
listEvents(t, client, lbID)
deleteNode(t, client, lbID, nodeID)
waitForLB(client, lbID, lbs.ACTIVE)
deleteLB(t, client, lbID)
}
func findServer(t *testing.T) string {
var serverIP string
client, err := newComputeClient()
th.AssertNoErr(t, err)
err = servers.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
sList, err := servers.ExtractServers(page)
th.AssertNoErr(t, err)
for _, s := range sList {
serverIP = s.AccessIPv4
t.Logf("Found an existing server: ID [%s] Public IP [%s]", s.ID, serverIP)
break
}
return true, nil
})
th.AssertNoErr(t, err)
if serverIP == "" {
t.Log("No server found, creating one")
imageRef := os.Getenv("RS_IMAGE_ID")
if imageRef == "" {
t.Fatalf("OS var RS_IMAGE_ID undefined")
}
flavorRef := os.Getenv("RS_FLAVOR_ID")
if flavorRef == "" {
t.Fatalf("OS var RS_FLAVOR_ID undefined")
}
opts := &servers.CreateOpts{
Name: tools.RandomString("lb_test_", 5),
ImageRef: imageRef,
FlavorRef: flavorRef,
DiskConfig: diskconfig.Manual,
}
s, err := servers.Create(client, opts).Extract()
th.AssertNoErr(t, err)
serverIP = s.AccessIPv4
t.Logf("Created server %s, waiting for it to build", s.ID)
err = servers.WaitForStatus(client, s.ID, "ACTIVE", 300)
th.AssertNoErr(t, err)
t.Logf("Server created successfully.")
}
return serverIP
}
func addNodes(t *testing.T, client *gophercloud.ServiceClient, lbID int, serverIP string) int {
opts := nodes.CreateOpts{
nodes.CreateOpt{
Address: serverIP,
Port: 80,
Condition: nodes.ENABLED,
Type: nodes.PRIMARY,
},
}
page := nodes.Create(client, lbID, opts)
nodeList, err := page.ExtractNodes()
th.AssertNoErr(t, err)
var nodeID int
for _, n := range nodeList {
nodeID = n.ID
}
if nodeID == 0 {
t.Fatalf("nodeID could not be extracted from create response")
}
t.Logf("Added node %d to LB %d", nodeID, lbID)
waitForLB(client, lbID, lbs.ACTIVE)
return nodeID
}
func listNodes(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
err := nodes.List(client, lbID, nil).EachPage(func(page pagination.Page) (bool, error) {
nodeList, err := nodes.ExtractNodes(page)
th.AssertNoErr(t, err)
for _, n := range nodeList {
t.Logf("Listing node: ID [%d] Address [%s:%d] Status [%s]", n.ID, n.Address, n.Port, n.Status)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func getNode(t *testing.T, client *gophercloud.ServiceClient, lbID int, nodeID int) {
node, err := nodes.Get(client, lbID, nodeID).Extract()
th.AssertNoErr(t, err)
t.Logf("Getting node %d: Type [%s] Weight [%d]", nodeID, node.Type, node.Weight)
}
func updateNode(t *testing.T, client *gophercloud.ServiceClient, lbID int, nodeID int) {
opts := nodes.UpdateOpts{
Weight: gophercloud.IntToPointer(10),
Condition: nodes.DRAINING,
Type: nodes.SECONDARY,
}
err := nodes.Update(client, lbID, nodeID, opts).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Updated node %d", nodeID)
waitForLB(client, lbID, lbs.ACTIVE)
}
func listEvents(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
pager := nodes.ListEvents(client, lbID, nodes.ListEventsOpts{})
err := pager.EachPage(func(page pagination.Page) (bool, error) {
eventList, err := nodes.ExtractNodeEvents(page)
th.AssertNoErr(t, err)
for _, e := range eventList {
t.Logf("Listing events for node %d: Type [%s] Msg [%s] Severity [%s] Date [%s]",
e.NodeID, e.Type, e.DetailedMessage, e.Severity, e.Created)
}
return true, nil
})
th.AssertNoErr(t, err)
}
func deleteNode(t *testing.T, client *gophercloud.ServiceClient, lbID int, nodeID int) {
err := nodes.Delete(client, lbID, nodeID).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Deleted node %d", nodeID)
}

View File

@ -0,0 +1,47 @@
// +build acceptance lbs
package v1
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/rackspace/lb/v1/sessions"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestSession(t *testing.T) {
client := setup(t)
ids := createLB(t, client, 1)
lbID := ids[0]
getSession(t, client, lbID)
enableSession(t, client, lbID)
waitForLB(client, lbID, "ACTIVE")
disableSession(t, client, lbID)
waitForLB(client, lbID, "ACTIVE")
deleteLB(t, client, lbID)
}
func getSession(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
sp, err := sessions.Get(client, lbID).Extract()
th.AssertNoErr(t, err)
t.Logf("Session config: Type [%s]", sp.Type)
}
func enableSession(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
opts := sessions.CreateOpts{Type: sessions.HTTPCOOKIE}
err := sessions.Enable(client, lbID, opts).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Enable %s sessions for %d", opts.Type, lbID)
}
func disableSession(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
err := sessions.Disable(client, lbID).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Disable sessions for %d", lbID)
}

View File

@ -0,0 +1,53 @@
// +build acceptance lbs
package v1
import (
"testing"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/rackspace/lb/v1/throttle"
th "github.com/rackspace/gophercloud/testhelper"
)
func TestThrottle(t *testing.T) {
client := setup(t)
ids := createLB(t, client, 1)
lbID := ids[0]
getThrottleConfig(t, client, lbID)
createThrottleConfig(t, client, lbID)
waitForLB(client, lbID, "ACTIVE")
deleteThrottleConfig(t, client, lbID)
waitForLB(client, lbID, "ACTIVE")
deleteLB(t, client, lbID)
}
func getThrottleConfig(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
sp, err := throttle.Get(client, lbID).Extract()
th.AssertNoErr(t, err)
t.Logf("Throttle config: MaxConns [%s]", sp.MaxConnections)
}
func createThrottleConfig(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
opts := throttle.CreateOpts{
MaxConnections: 200,
MaxConnectionRate: 100,
MinConnections: 0,
RateInterval: 10,
}
err := throttle.Create(client, lbID, opts).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Enable throttling for %d", lbID)
}
func deleteThrottleConfig(t *testing.T, client *gophercloud.ServiceClient, lbID int) {
err := throttle.Delete(client, lbID).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Disable throttling for %d", lbID)
}

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