mirror of https://github.com/docker/docs.git
Add dependencies for OpenStack driver
Signed-off-by: Guillaume Giamarchi <guillaume.giamarchi@gmail.com>
This commit is contained in:
parent
c2f1fea9dc
commit
1785869490
|
@ -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"
|
||||||
|
|
|
@ -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.
|
|
@ -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.
|
84
Godeps/_workspace/src/github.com/mitchellh/mapstructure/decode_hooks.go
generated
vendored
Normal file
84
Godeps/_workspace/src/github.com/mitchellh/mapstructure/decode_hooks.go
generated
vendored
Normal 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
|
||||||
|
}
|
191
Godeps/_workspace/src/github.com/mitchellh/mapstructure/decode_hooks_test.go
generated
vendored
Normal file
191
Godeps/_workspace/src/github.com/mitchellh/mapstructure/decode_hooks_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
704
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure.go
generated
vendored
Normal file
704
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure.go
generated
vendored
Normal 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
|
||||||
|
}
|
||||||
|
}
|
243
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_benchmark_test.go
generated
vendored
Normal file
243
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_benchmark_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
47
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_bugs_test.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_bugs_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
169
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_examples_test.go
generated
vendored
Normal file
169
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_examples_test.go
generated
vendored
Normal 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}
|
||||||
|
}
|
828
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_test.go
generated
vendored
Normal file
828
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
bin/*
|
||||||
|
pkg/*
|
|
@ -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.
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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"])
|
||||||
|
}
|
||||||
|
}
|
14
Godeps/_workspace/src/github.com/rackspace/gophercloud/.travis.yml
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/rackspace/gophercloud/.travis.yml
generated
vendored
Normal 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
|
275
Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTING.md
generated
vendored
Normal file
275
Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTING.md
generated
vendored
Normal 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!
|
12
Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTORS.md
generated
vendored
Normal file
12
Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTORS.md
generated
vendored
Normal 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>
|
5
Godeps/_workspace/src/github.com/rackspace/gophercloud/Godeps/Godeps.json
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/rackspace/gophercloud/Godeps/Godeps.json
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/rackspace/gophercloud",
|
||||||
|
"GoVersion": "go1.3.3",
|
||||||
|
"Deps": []
|
||||||
|
}
|
5
Godeps/_workspace/src/github.com/rackspace/gophercloud/Godeps/Readme
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/rackspace/gophercloud/Godeps/Readme
generated
vendored
Normal 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.
|
|
@ -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
|
|
@ -0,0 +1,161 @@
|
||||||
|
# Gophercloud: the OpenStack SDK for Go
|
||||||
|
[](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).
|
338
Godeps/_workspace/src/github.com/rackspace/gophercloud/UPGRADING.md
generated
vendored
Normal file
338
Godeps/_workspace/src/github.com/rackspace/gophercloud/UPGRADING.md
generated
vendored
Normal 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.
|
57
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/README.md
generated
vendored
Normal file
57
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/README.md
generated
vendored
Normal 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
|
||||||
|
```
|
70
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/blockstorage/v1/snapshots_test.go
generated
vendored
Normal file
70
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/blockstorage/v1/snapshots_test.go
generated
vendored
Normal 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")
|
||||||
|
}
|
63
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/blockstorage/v1/volumes_test.go
generated
vendored
Normal file
63
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/blockstorage/v1/volumes_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
49
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/blockstorage/v1/volumetypes_test.go
generated
vendored
Normal file
49
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/blockstorage/v1/volumetypes_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
40
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/client_test.go
generated
vendored
Normal file
40
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/client_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
55
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/bootfromvolume_test.go
generated
vendored
Normal file
55
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/bootfromvolume_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
97
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/compute_test.go
generated
vendored
Normal file
97
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/compute_test.go
generated
vendored
Normal 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
|
||||||
|
}
|
47
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/extension_test.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/extension_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
57
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/flavors_test.go
generated
vendored
Normal file
57
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/flavors_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
37
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/images_test.go
generated
vendored
Normal file
37
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/images_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
74
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/keypairs_test.go
generated
vendored
Normal file
74
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/keypairs_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/pkg.go
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/pkg.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// The v2 package contains acceptance tests for the Openstack Compute V2 service.
|
||||||
|
|
||||||
|
package v2
|
72
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/secdefrules_test.go
generated
vendored
Normal file
72
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/secdefrules_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
177
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/secgroup_test.go
generated
vendored
Normal file
177
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/secgroup_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
450
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/servers_test.go
generated
vendored
Normal file
450
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/servers_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/extension_test.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/extension_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
47
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/identity_test.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/identity_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/pkg.go
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/pkg.go
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package v2
|
58
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/role_test.go
generated
vendored
Normal file
58
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/role_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/tenant_test.go
generated
vendored
Normal file
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/tenant_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
38
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/token_test.go
generated
vendored
Normal file
38
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/token_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/user_test.go
generated
vendored
Normal file
127
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v2/user_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
111
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v3/endpoint_test.go
generated
vendored
Normal file
111
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v3/endpoint_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
39
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v3/identity_test.go
generated
vendored
Normal file
39
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v3/identity_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v3/pkg.go
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v3/pkg.go
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package v3
|
36
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v3/service_test.go
generated
vendored
Normal file
36
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v3/service_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
42
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v3/token_test.go
generated
vendored
Normal file
42
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/identity/v3/token_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
51
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/apiversion_test.go
generated
vendored
Normal file
51
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/apiversion_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
39
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/common.go
generated
vendored
Normal file
39
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/common.go
generated
vendored
Normal 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
|
||||||
|
}
|
45
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extension_test.go
generated
vendored
Normal file
45
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extension_test.go
generated
vendored
Normal 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")
|
||||||
|
}
|
300
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/layer3_test.go
generated
vendored
Normal file
300
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/layer3_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
78
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/common.go
generated
vendored
Normal file
78
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/common.go
generated
vendored
Normal 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
|
||||||
|
}
|
95
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/member_test.go
generated
vendored
Normal file
95
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/member_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
77
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/monitor_test.go
generated
vendored
Normal file
77
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/monitor_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/pkg.go
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/pkg.go
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package lbaas
|
98
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/pool_test.go
generated
vendored
Normal file
98
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/pool_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
101
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/vip_test.go
generated
vendored
Normal file
101
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/lbaas/vip_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/pkg.go
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/pkg.go
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package extensions
|
68
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/provider_test.go
generated
vendored
Normal file
68
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/provider_test.go
generated
vendored
Normal 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()
|
||||||
|
}
|
171
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/security_test.go
generated
vendored
Normal file
171
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/extensions/security_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
68
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/network_test.go
generated
vendored
Normal file
68
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/network_test.go
generated
vendored
Normal 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()
|
||||||
|
}
|
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/pkg.go
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/pkg.go
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package v2
|
117
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/port_test.go
generated
vendored
Normal file
117
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/port_test.go
generated
vendored
Normal 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
|
||||||
|
}
|
86
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/subnet_test.go
generated
vendored
Normal file
86
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/networking/v2/subnet_test.go
generated
vendored
Normal 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
|
||||||
|
}
|
44
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/accounts_test.go
generated
vendored
Normal file
44
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/accounts_test.go
generated
vendored
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/common.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/common.go
generated
vendored
Normal 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
|
||||||
|
}
|
89
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/containers_test.go
generated
vendored
Normal file
89
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/containers_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
119
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/objects_test.go
generated
vendored
Normal file
119
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/objectstorage/v1/objects_test.go
generated
vendored
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/pkg.go
generated
vendored
Normal file
4
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/pkg.go
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// +build acceptance
|
||||||
|
|
||||||
|
package openstack
|
||||||
|
|
38
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/blockstorage/v1/common.go
generated
vendored
Normal file
38
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/blockstorage/v1/common.go
generated
vendored
Normal 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
|
||||||
|
}
|
82
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/blockstorage/v1/snapshot_test.go
generated
vendored
Normal file
82
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/blockstorage/v1/snapshot_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
71
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/blockstorage/v1/volume_test.go
generated
vendored
Normal file
71
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/blockstorage/v1/volume_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/blockstorage/v1/volume_type_test.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/blockstorage/v1/volume_type_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
28
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/client_test.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/client_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/bootfromvolume_test.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/bootfromvolume_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
60
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/compute_test.go
generated
vendored
Normal file
60
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/compute_test.go
generated
vendored
Normal 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
|
||||||
|
}
|
61
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/flavors_test.go
generated
vendored
Normal file
61
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/flavors_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
63
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/images_test.go
generated
vendored
Normal file
63
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/images_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
87
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/keypairs_test.go
generated
vendored
Normal file
87
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/keypairs_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
53
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/networks_test.go
generated
vendored
Normal file
53
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/networks_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/pkg.go
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/pkg.go
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package v2
|
204
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/servers_test.go
generated
vendored
Normal file
204
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/servers_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
53
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/virtualinterfaces_test.go
generated
vendored
Normal file
53
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/compute/v2/virtualinterfaces_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
54
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/extension_test.go
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/extension_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
50
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/identity_test.go
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/identity_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/pkg.go
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/pkg.go
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package v2
|
59
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/role_test.go
generated
vendored
Normal file
59
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/role_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
37
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/tenant_test.go
generated
vendored
Normal file
37
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/tenant_test.go
generated
vendored
Normal 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.")
|
||||||
|
}
|
||||||
|
}
|
93
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/user_test.go
generated
vendored
Normal file
93
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/user_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
94
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/acl_test.go
generated
vendored
Normal file
94
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/acl_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
62
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/common.go
generated
vendored
Normal file
62
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/common.go
generated
vendored
Normal 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, ", ")
|
||||||
|
}
|
214
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/lb_test.go
generated
vendored
Normal file
214
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/lb_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
60
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/monitor_test.go
generated
vendored
Normal file
60
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/monitor_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
175
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/node_test.go
generated
vendored
Normal file
175
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/node_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
47
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/session_test.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/session_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
53
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/throttle_test.go
generated
vendored
Normal file
53
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/lb/v1/throttle_test.go
generated
vendored
Normal 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
Loading…
Reference in New Issue