Merge pull request #513 from kyarovoy/patch-1
Update provider_client.go
diff --git a/acceptance/openstack/compute/v2/floatingip_test.go b/acceptance/openstack/compute/v2/floatingip_test.go
index ab7554b..de6efc9 100644
--- a/acceptance/openstack/compute/v2/floatingip_test.go
+++ b/acceptance/openstack/compute/v2/floatingip_test.go
@@ -49,7 +49,9 @@
return fip, err
}
-func associateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, serverId string, fip *floatingip.FloatingIP) {
+func associateFloatingIPDeprecated(t *testing.T, client *gophercloud.ServiceClient, serverId string, fip *floatingip.FloatingIP) {
+ // This form works, but is considered deprecated.
+ // See associateFloatingIP or associateFloatingIPFixed
err := floatingip.Associate(client, serverId, fip.IP).ExtractErr()
th.AssertNoErr(t, err)
t.Logf("Associated floating IP %v from instance %v", fip.IP, serverId)
@@ -63,6 +65,63 @@
t.Logf("Floating IP %v is associated with Fixed IP %v", fip.IP, floatingIp.FixedIP)
}
+func associateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, serverId string, fip *floatingip.FloatingIP) {
+ associateOpts := floatingip.AssociateOpts{
+ ServerID: serverId,
+ FloatingIP: fip.IP,
+ }
+
+ err := floatingip.AssociateInstance(client, associateOpts).ExtractErr()
+ th.AssertNoErr(t, err)
+ t.Logf("Associated floating IP %v from instance %v", fip.IP, serverId)
+ defer func() {
+ err = floatingip.DisassociateInstance(client, associateOpts).ExtractErr()
+ th.AssertNoErr(t, err)
+ t.Logf("Disassociated floating IP %v from instance %v", fip.IP, serverId)
+ }()
+ floatingIp, err := floatingip.Get(client, fip.ID).Extract()
+ th.AssertNoErr(t, err)
+ t.Logf("Floating IP %v is associated with Fixed IP %v", fip.IP, floatingIp.FixedIP)
+}
+
+func associateFloatingIPFixed(t *testing.T, client *gophercloud.ServiceClient, serverId string, fip *floatingip.FloatingIP) {
+
+ network := os.Getenv("OS_NETWORK_NAME")
+ server, err := servers.Get(client, serverId).Extract()
+ if err != nil {
+ t.Fatalf("%s", err)
+ }
+
+ var fixedIP string
+ for _, networkAddresses := range server.Addresses[network].([]interface{}) {
+ address := networkAddresses.(map[string]interface{})
+ if address["OS-EXT-IPS:type"] == "fixed" {
+ if address["version"].(float64) == 4 {
+ fixedIP = address["addr"].(string)
+ }
+ }
+ }
+
+ associateOpts := floatingip.AssociateOpts{
+ ServerID: serverId,
+ FloatingIP: fip.IP,
+ FixedIP: fixedIP,
+ }
+
+ err = floatingip.AssociateInstance(client, associateOpts).ExtractErr()
+ th.AssertNoErr(t, err)
+ t.Logf("Associated floating IP %v from instance %v with Fixed IP %v", fip.IP, serverId, fixedIP)
+ defer func() {
+ err = floatingip.DisassociateInstance(client, associateOpts).ExtractErr()
+ th.AssertNoErr(t, err)
+ t.Logf("Disassociated floating IP %v from instance %v with Fixed IP %v", fip.IP, serverId, fixedIP)
+ }()
+ floatingIp, err := floatingip.Get(client, fip.ID).Extract()
+ th.AssertNoErr(t, err)
+ th.AssertEquals(t, floatingIp.FixedIP, fixedIP)
+ t.Logf("Floating IP %v is associated with Fixed IP %v", fip.IP, floatingIp.FixedIP)
+}
+
func TestFloatingIP(t *testing.T) {
pool := os.Getenv("OS_POOL_NAME")
if pool == "" {
@@ -102,6 +161,8 @@
t.Logf("Floating IP deleted.")
}()
+ associateFloatingIPDeprecated(t, client, server.ID, fip)
associateFloatingIP(t, client, server.ID, fip)
+ associateFloatingIPFixed(t, client, server.ID, fip)
}
diff --git a/acceptance/openstack/identity/v2/token_test.go b/acceptance/openstack/identity/v2/token_test.go
index d903140..e01b3b3 100644
--- a/acceptance/openstack/identity/v2/token_test.go
+++ b/acceptance/openstack/identity/v2/token_test.go
@@ -9,7 +9,8 @@
th "github.com/rackspace/gophercloud/testhelper"
)
-func TestAuthenticate(t *testing.T) {
+func TestAuthenticateAndValidate(t *testing.T) {
+ // 1. TestAuthenticate
ao := v2AuthOptions(t)
service := unauthenticatedClient(t)
@@ -35,4 +36,19 @@
t.Logf(" - region=[%s] publicURL=[%s]", endpoint.Region, endpoint.PublicURL)
}
}
+
+ // 2. TestValidate
+ client := authenticatedClient(t)
+
+ // Validate Token!
+ getResult := tokens2.Get(client, token.ID)
+
+ // Extract and print the user.
+ user, err := getResult.ExtractUser()
+ th.AssertNoErr(t, err)
+
+ t.Logf("Acquired User: [%s]", user.Name)
+ t.Logf("The User id: [%s]", user.ID)
+ t.Logf("The User username: [%s]", user.UserName)
+ t.Logf("The User roles: [%#v]", user.Roles)
}
diff --git a/acceptance/openstack/networking/v2/port_test.go b/acceptance/openstack/networking/v2/port_test.go
index 03e8e27..91bf5bd 100644
--- a/acceptance/openstack/networking/v2/port_test.go
+++ b/acceptance/openstack/networking/v2/port_test.go
@@ -45,10 +45,20 @@
th.AssertEquals(t, p.ID, portID)
// Update port
- p, err = ports.Update(Client, portID, ports.UpdateOpts{Name: "new_port_name"}).Extract()
+ updateOpts := ports.UpdateOpts{
+ Name: "new_port_name",
+ AllowedAddressPairs: []ports.AddressPair{
+ ports.AddressPair{IPAddress: "192.168.199.201"},
+ },
+ }
+ p, err = ports.Update(Client, portID, updateOpts).Extract()
+
th.AssertNoErr(t, err)
th.AssertEquals(t, p.Name, "new_port_name")
+ updatedPort, err := ports.Get(Client, portID).Extract()
+ th.AssertEquals(t, updatedPort.AllowedAddressPairs[0].IPAddress, "192.168.199.201")
+
// Delete port
res := ports.Delete(Client, portID)
th.AssertNoErr(t, res.Err)
@@ -82,8 +92,8 @@
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)
+ t.Logf("Port: ID [%s] Name [%s] Status [%s] MAC addr [%s] Fixed IPs [%#v] Security groups [%#v] Allowed Address Pairs [%#v]",
+ p.ID, p.Name, p.Status, p.MACAddress, p.FixedIPs, p.SecurityGroups, p.AllowedAddressPairs)
}
return true, nil
@@ -108,6 +118,9 @@
IPVersion: subnets.IPv4,
Name: "my_subnet",
EnableDHCP: subnets.Down,
+ AllocationPools: []subnets.AllocationPool{
+ subnets.AllocationPool{Start: "192.168.199.2", End: "192.168.199.200"},
+ },
}).Extract()
return s.ID, err
}
diff --git a/openstack/client.go b/openstack/client.go
index b30d205..33602a6 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -167,6 +167,7 @@
if options.AllowReauth {
client.ReauthFunc = func() error {
+ client.TokenID = ""
return AuthenticateV3(client, options)
}
}
diff --git a/openstack/compute/v2/extensions/floatingip/fixtures.go b/openstack/compute/v2/extensions/floatingip/fixtures.go
index 26f3299..e47fa4c 100644
--- a/openstack/compute/v2/extensions/floatingip/fixtures.go
+++ b/openstack/compute/v2/extensions/floatingip/fixtures.go
@@ -155,6 +155,25 @@
})
}
+// HandleFixedAssociateSucessfully configures the test server to respond to a Post request
+// to associate an allocated floating IP with a specific fixed IP address
+func HandleAssociateFixedSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/action", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestJSONRequest(t, r, `
+{
+ "addFloatingIp": {
+ "address": "10.10.10.2",
+ "fixed_address": "166.78.185.201"
+ }
+}
+`)
+
+ w.WriteHeader(http.StatusAccepted)
+ })
+}
+
// HandleDisassociateSuccessfully configures the test server to respond to a Post request
// to disassociate an allocated floating IP
func HandleDisassociateSuccessfully(t *testing.T) {
diff --git a/openstack/compute/v2/extensions/floatingip/requests.go b/openstack/compute/v2/extensions/floatingip/requests.go
index 8abb72d..8206462 100644
--- a/openstack/compute/v2/extensions/floatingip/requests.go
+++ b/openstack/compute/v2/extensions/floatingip/requests.go
@@ -26,6 +26,18 @@
Pool string
}
+// AssociateOpts specifies the required information to associate or disassociate a floating IP to an instance
+type AssociateOpts struct {
+ // ServerID is the UUID of the server
+ ServerID string
+
+ // FixedIP is an optional fixed IP address of the server
+ FixedIP string
+
+ // FloatingIP is the floating IP to associate with an instance
+ FloatingIP string
+}
+
// ToFloatingIPCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
if opts.Pool == "" {
@@ -35,6 +47,26 @@
return map[string]interface{}{"pool": opts.Pool}, nil
}
+// ToAssociateMap constructs a request body from AssociateOpts.
+func (opts AssociateOpts) ToAssociateMap() (map[string]interface{}, error) {
+ if opts.ServerID == "" {
+ return nil, errors.New("Required field missing for floating IP association: ServerID")
+ }
+
+ if opts.FloatingIP == "" {
+ return nil, errors.New("Required field missing for floating IP association: FloatingIP")
+ }
+
+ associateInfo := map[string]interface{}{
+ "serverId": opts.ServerID,
+ "floatingIp": opts.FloatingIP,
+ "fixedIp": opts.FixedIP,
+ }
+
+ return associateInfo, nil
+
+}
+
// Create requests the creation of a new floating IP
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
@@ -68,6 +100,7 @@
// association / disassociation
// Associate pairs an allocated floating IP with an instance
+// Deprecated. Use AssociateInstance.
func Associate(client *gophercloud.ServiceClient, serverId, fip string) AssociateResult {
var res AssociateResult
@@ -79,7 +112,33 @@
return res
}
+// AssociateInstance pairs an allocated floating IP with an instance.
+func AssociateInstance(client *gophercloud.ServiceClient, opts AssociateOpts) AssociateResult {
+ var res AssociateResult
+
+ associateInfo, err := opts.ToAssociateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
+ addFloatingIp := make(map[string]interface{})
+ addFloatingIp["address"] = associateInfo["floatingIp"].(string)
+
+ // fixedIp is not required
+ if associateInfo["fixedIp"] != "" {
+ addFloatingIp["fixed_address"] = associateInfo["fixedIp"].(string)
+ }
+
+ serverId := associateInfo["serverId"].(string)
+
+ reqBody := map[string]interface{}{"addFloatingIp": addFloatingIp}
+ _, res.Err = client.Post(associateURL(client, serverId), reqBody, nil, nil)
+ return res
+}
+
// Disassociate decouples an allocated floating IP from an instance
+// Deprecated. Use DisassociateInstance.
func Disassociate(client *gophercloud.ServiceClient, serverId, fip string) DisassociateResult {
var res DisassociateResult
@@ -90,3 +149,23 @@
_, res.Err = client.Post(disassociateURL(client, serverId), reqBody, nil, nil)
return res
}
+
+// DisassociateInstance decouples an allocated floating IP from an instance
+func DisassociateInstance(client *gophercloud.ServiceClient, opts AssociateOpts) DisassociateResult {
+ var res DisassociateResult
+
+ associateInfo, err := opts.ToAssociateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
+ removeFloatingIp := make(map[string]interface{})
+ removeFloatingIp["address"] = associateInfo["floatingIp"].(string)
+ reqBody := map[string]interface{}{"removeFloatingIp": removeFloatingIp}
+
+ serverId := associateInfo["serverId"].(string)
+
+ _, res.Err = client.Post(disassociateURL(client, serverId), reqBody, nil, nil)
+ return res
+}
diff --git a/openstack/compute/v2/extensions/floatingip/requests_test.go b/openstack/compute/v2/extensions/floatingip/requests_test.go
index ed2460e..4d86fe2 100644
--- a/openstack/compute/v2/extensions/floatingip/requests_test.go
+++ b/openstack/compute/v2/extensions/floatingip/requests_test.go
@@ -57,7 +57,7 @@
th.AssertNoErr(t, err)
}
-func TestAssociate(t *testing.T) {
+func TestAssociateDeprecated(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleAssociateSuccessfully(t)
@@ -68,7 +68,36 @@
th.AssertNoErr(t, err)
}
-func TestDisassociate(t *testing.T) {
+func TestAssociate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleAssociateSuccessfully(t)
+
+ associateOpts := AssociateOpts{
+ ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
+ FloatingIP: "10.10.10.2",
+ }
+
+ err := AssociateInstance(client.ServiceClient(), associateOpts).ExtractErr()
+ th.AssertNoErr(t, err)
+}
+
+func TestAssociateFixed(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleAssociateFixedSuccessfully(t)
+
+ associateOpts := AssociateOpts{
+ ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
+ FloatingIP: "10.10.10.2",
+ FixedIP: "166.78.185.201",
+ }
+
+ err := AssociateInstance(client.ServiceClient(), associateOpts).ExtractErr()
+ th.AssertNoErr(t, err)
+}
+
+func TestDisassociateDeprecated(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleDisassociateSuccessfully(t)
@@ -78,3 +107,17 @@
err := Disassociate(client.ServiceClient(), serverId, fip).ExtractErr()
th.AssertNoErr(t, err)
}
+
+func TestDisassociateInstance(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleDisassociateSuccessfully(t)
+
+ associateOpts := AssociateOpts{
+ ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
+ FloatingIP: "10.10.10.2",
+ }
+
+ err := DisassociateInstance(client.ServiceClient(), associateOpts).ExtractErr()
+ th.AssertNoErr(t, err)
+}
diff --git a/openstack/db/v1/configurations/results.go b/openstack/db/v1/configurations/results.go
index 37a77ad..d0d1d6e 100644
--- a/openstack/db/v1/configurations/results.go
+++ b/openstack/db/v1/configurations/results.go
@@ -1,6 +1,8 @@
package configurations
import (
+ "fmt"
+ "reflect"
"time"
"github.com/mitchellh/mapstructure"
@@ -43,12 +45,18 @@
Configs []Config `mapstructure:"configurations" json:"configurations"`
}
- err := mapstructure.Decode(casted, &resp)
+ if err := mapstructure.Decode(casted, &resp); err != nil {
+ return nil, err
+ }
var vals []interface{}
- switch (casted).(type) {
- case interface{}:
+ switch casted.(type) {
+ case map[string]interface{}:
vals = casted.(map[string]interface{})["configurations"].([]interface{})
+ case map[string][]interface{}:
+ vals = casted.(map[string][]interface{})["configurations"]
+ default:
+ return resp.Configs, fmt.Errorf("Unknown type: %v", reflect.TypeOf(casted))
}
for i, v := range vals {
@@ -71,7 +79,7 @@
}
}
- return resp.Configs, err
+ return resp.Configs, nil
}
type commonResult struct {
diff --git a/openstack/db/v1/instances/results.go b/openstack/db/v1/instances/results.go
index 9a49510..95aed16 100644
--- a/openstack/db/v1/instances/results.go
+++ b/openstack/db/v1/instances/results.go
@@ -1,6 +1,8 @@
package instances
import (
+ "fmt"
+ "reflect"
"time"
"github.com/mitchellh/mapstructure"
@@ -142,16 +144,22 @@
func ExtractInstances(page pagination.Page) ([]Instance, error) {
casted := page.(InstancePage).Body
- var response struct {
+ var resp struct {
Instances []Instance `mapstructure:"instances"`
}
- err := mapstructure.Decode(casted, &response)
+ if err := mapstructure.Decode(casted, &resp); err != nil {
+ return nil, err
+ }
var vals []interface{}
- switch (casted).(type) {
- case interface{}:
+ switch casted.(type) {
+ case map[string]interface{}:
vals = casted.(map[string]interface{})["instances"].([]interface{})
+ case map[string][]interface{}:
+ vals = casted.(map[string][]interface{})["instances"]
+ default:
+ return resp.Instances, fmt.Errorf("Unknown type: %v", reflect.TypeOf(casted))
}
for i, v := range vals {
@@ -160,21 +168,21 @@
if t, ok := val["created"].(string); ok && t != "" {
creationTime, err := time.Parse(time.RFC3339, t)
if err != nil {
- return response.Instances, err
+ return resp.Instances, err
}
- response.Instances[i].Created = creationTime
+ resp.Instances[i].Created = creationTime
}
if t, ok := val["updated"].(string); ok && t != "" {
updatedTime, err := time.Parse(time.RFC3339, t)
if err != nil {
- return response.Instances, err
+ return resp.Instances, err
}
- response.Instances[i].Updated = updatedTime
+ resp.Instances[i].Updated = updatedTime
}
}
- return response.Instances, err
+ return resp.Instances, nil
}
// UserRootResult represents the result of an operation to enable the root user.
diff --git a/openstack/identity/v2/tokens/fixtures.go b/openstack/identity/v2/tokens/fixtures.go
index 1cb0d05..6245259 100644
--- a/openstack/identity/v2/tokens/fixtures.go
+++ b/openstack/identity/v2/tokens/fixtures.go
@@ -10,6 +10,7 @@
"github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
th "github.com/rackspace/gophercloud/testhelper"
+ thclient "github.com/rackspace/gophercloud/testhelper/client"
)
// ExpectedToken is the token that should be parsed from TokenCreationResponse.
@@ -54,6 +55,14 @@
},
}
+// ExpectedUser is the token that should be parsed from TokenGetResponse.
+var ExpectedUser = &User{
+ ID: "a530fefc3d594c4ba2693a4ecd6be74e",
+ Name: "apiserver",
+ Roles: []Role{{"member"}, {"service"}},
+ UserName: "apiserver",
+}
+
// TokenCreationResponse is a JSON response that contains ExpectedToken and ExpectedServiceCatalog.
const TokenCreationResponse = `
{
@@ -99,6 +108,39 @@
}
`
+// TokenGetResponse is a JSON response that contains ExpectedToken and ExpectedUser.
+const TokenGetResponse = `
+{
+ "access": {
+ "token": {
+ "issued_at": "2014-01-30T15:30:58.000000Z",
+ "expires": "2014-01-31T15:30:58Z",
+ "id": "aaaabbbbccccdddd",
+ "tenant": {
+ "description": "There are many tenants. This one is yours.",
+ "enabled": true,
+ "id": "fc394f2ab2df4114bde39905f800dc57",
+ "name": "test"
+ }
+ },
+ "serviceCatalog": [],
+ "user": {
+ "id": "a530fefc3d594c4ba2693a4ecd6be74e",
+ "name": "apiserver",
+ "roles": [
+ {
+ "name": "member"
+ },
+ {
+ "name": "service"
+ }
+ ],
+ "roles_links": [],
+ "username": "apiserver"
+ }
+ }
+}`
+
// HandleTokenPost expects a POST against a /tokens handler, ensures that the request body has been
// constructed properly given certain auth options, and returns the result.
func HandleTokenPost(t *testing.T, requestJSON string) {
@@ -115,6 +157,19 @@
})
}
+// HandleTokenGet expects a Get against a /tokens handler, ensures that the request body has been
+// constructed properly given certain auth options, and returns the result.
+func HandleTokenGet(t *testing.T, token string) {
+ th.Mux.HandleFunc("/tokens/"+token, func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestHeader(t, r, "X-Auth-Token", thclient.TokenID)
+
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, TokenGetResponse)
+ })
+}
+
// IsSuccessful ensures that a CreateResult was successful and contains the correct token and
// service catalog.
func IsSuccessful(t *testing.T, result CreateResult) {
@@ -126,3 +181,15 @@
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ExpectedServiceCatalog, serviceCatalog)
}
+
+// GetIsSuccessful ensures that a GetResult was successful and contains the correct token and
+// User Info.
+func GetIsSuccessful(t *testing.T, result GetResult) {
+ token, err := result.ExtractToken()
+ th.AssertNoErr(t, err)
+ th.CheckDeepEquals(t, ExpectedToken, token)
+
+ user, err := result.ExtractUser()
+ th.AssertNoErr(t, err)
+ th.CheckDeepEquals(t, ExpectedUser, user)
+}
diff --git a/openstack/identity/v2/tokens/requests.go b/openstack/identity/v2/tokens/requests.go
index 074a89e..1f51438 100644
--- a/openstack/identity/v2/tokens/requests.go
+++ b/openstack/identity/v2/tokens/requests.go
@@ -88,3 +88,12 @@
})
return result
}
+
+// Validates and retrieves information for user's token.
+func Get(client *gophercloud.ServiceClient, token string) GetResult {
+ var result GetResult
+ _, result.Err = client.Get(GetURL(client, token), &result.Body, &gophercloud.RequestOpts{
+ OkCodes: []int{200, 203},
+ })
+ return result
+}
diff --git a/openstack/identity/v2/tokens/requests_test.go b/openstack/identity/v2/tokens/requests_test.go
index 8b78c85..f1ec339 100644
--- a/openstack/identity/v2/tokens/requests_test.go
+++ b/openstack/identity/v2/tokens/requests_test.go
@@ -139,3 +139,14 @@
tokenPostErr(t, options, ErrPasswordRequired)
}
+
+func tokenGet(t *testing.T, tokenId string) GetResult {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleTokenGet(t, tokenId)
+ return Get(client.ServiceClient(), tokenId)
+}
+
+func TestGetWithToken(t *testing.T) {
+ GetIsSuccessful(t, tokenGet(t, "db22caf43c934e6c829087c41ff8d8d6"))
+}
diff --git a/openstack/identity/v2/tokens/results.go b/openstack/identity/v2/tokens/results.go
index 1eddb9d..67c577b 100644
--- a/openstack/identity/v2/tokens/results.go
+++ b/openstack/identity/v2/tokens/results.go
@@ -25,6 +25,17 @@
Tenant tenants.Tenant
}
+// Authorization need user info which can get from token authentication's response
+type Role struct {
+ Name string `mapstructure:"name"`
+}
+type User struct {
+ ID string `mapstructure:"id"`
+ Name string `mapstructure:"name"`
+ UserName string `mapstructure:"username"`
+ Roles []Role `mapstructure:"roles"`
+}
+
// Endpoint represents a single API endpoint offered by a service.
// It provides the public and internal URLs, if supported, along with a region specifier, again if provided.
// The significance of the Region field will depend upon your provider.
@@ -74,6 +85,12 @@
gophercloud.Result
}
+// GetResult is the deferred response from a Get call, which is the same with a Created token.
+// Use ExtractUser() to interpret it as a User.
+type GetResult struct {
+ CreateResult
+}
+
// ExtractToken returns the just-created Token from a CreateResult.
func (result CreateResult) ExtractToken() (*Token, error) {
if result.Err != nil {
@@ -131,3 +148,23 @@
func createErr(err error) CreateResult {
return CreateResult{gophercloud.Result{Err: err}}
}
+
+// ExtractUser returns the User from a GetResult.
+func (result GetResult) ExtractUser() (*User, error) {
+ if result.Err != nil {
+ return nil, result.Err
+ }
+
+ var response struct {
+ Access struct {
+ User User `mapstructure:"user"`
+ } `mapstructure:"access"`
+ }
+
+ err := mapstructure.Decode(result.Body, &response)
+ if err != nil {
+ return nil, err
+ }
+
+ return &response.Access.User, nil
+}
diff --git a/openstack/identity/v2/tokens/urls.go b/openstack/identity/v2/tokens/urls.go
index cd4c696..ee13932 100644
--- a/openstack/identity/v2/tokens/urls.go
+++ b/openstack/identity/v2/tokens/urls.go
@@ -6,3 +6,8 @@
func CreateURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("tokens")
}
+
+// GetURL generates the URL used to Validate Tokens.
+func GetURL(client *gophercloud.ServiceClient, token string) string {
+ return client.ServiceURL("tokens", token)
+}
diff --git a/openstack/identity/v3/tokens/requests.go b/openstack/identity/v3/tokens/requests.go
index d449ca3..d63b1bb 100644
--- a/openstack/identity/v3/tokens/requests.go
+++ b/openstack/identity/v3/tokens/requests.go
@@ -15,9 +15,9 @@
}
func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
- h := c.AuthenticatedHeaders()
- h["X-Subject-Token"] = subjectToken
- return h
+ return map[string]string{
+ "X-Subject-Token": subjectToken,
+ }
}
// Create authenticates and either generates a new token, or changes the Scope of an existing token.
diff --git a/openstack/networking/v2/extensions/layer3/routers/requests.go b/openstack/networking/v2/extensions/layer3/routers/requests.go
old mode 100755
new mode 100644
index 077a717..1ffc136
--- a/openstack/networking/v2/extensions/layer3/routers/requests.go
+++ b/openstack/networking/v2/extensions/layer3/routers/requests.go
@@ -16,6 +16,7 @@
ID string `q:"id"`
Name string `q:"name"`
AdminStateUp *bool `q:"admin_state_up"`
+ Distributed *bool `q:"distributed"`
Status string `q:"status"`
TenantID string `q:"tenant_id"`
Limit int `q:"limit"`
@@ -46,6 +47,7 @@
type CreateOpts struct {
Name string
AdminStateUp *bool
+ Distributed *bool
TenantID string
GatewayInfo *GatewayInfo
}
@@ -62,6 +64,7 @@
type router struct {
Name *string `json:"name,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
+ Distributed *bool `json:"distributed,omitempty"`
TenantID *string `json:"tenant_id,omitempty"`
GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"`
}
@@ -73,6 +76,7 @@
reqBody := request{Router: router{
Name: gophercloud.MaybeString(opts.Name),
AdminStateUp: opts.AdminStateUp,
+ Distributed: opts.Distributed,
TenantID: gophercloud.MaybeString(opts.TenantID),
}}
@@ -96,7 +100,9 @@
type UpdateOpts struct {
Name string
AdminStateUp *bool
+ Distributed *bool
GatewayInfo *GatewayInfo
+ Routes []Route
}
// Update allows routers to be updated. You can update the name, administrative
@@ -108,7 +114,9 @@
type router struct {
Name *string `json:"name,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
+ Distributed *bool `json:"distributed,omitempty"`
GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"`
+ Routes []Route `json:"routes"`
}
type request struct {
@@ -118,12 +126,17 @@
reqBody := request{Router: router{
Name: gophercloud.MaybeString(opts.Name),
AdminStateUp: opts.AdminStateUp,
+ Distributed: opts.Distributed,
}}
if opts.GatewayInfo != nil {
reqBody.Router.GatewayInfo = opts.GatewayInfo
}
+ if opts.Routes != nil {
+ reqBody.Router.Routes = opts.Routes
+ }
+
// Send request to API
var res UpdateResult
_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
diff --git a/openstack/networking/v2/extensions/layer3/routers/requests_test.go b/openstack/networking/v2/extensions/layer3/routers/requests_test.go
old mode 100755
new mode 100644
index c34264d..dbdc6fa
--- a/openstack/networking/v2/extensions/layer3/routers/requests_test.go
+++ b/openstack/networking/v2/extensions/layer3/routers/requests_test.go
@@ -37,6 +37,7 @@
"name": "second_routers",
"admin_state_up": true,
"tenant_id": "6b96ff0cb17a4b859e1e575d221683d3",
+ "distributed": false,
"id": "7177abc4-5ae9-4bb7-b0d4-89e94a4abf3b"
},
{
@@ -47,6 +48,7 @@
"name": "router1",
"admin_state_up": true,
"tenant_id": "33a40233088643acb66ff6eb0ebea679",
+ "distributed": false,
"id": "a9254bdb-2613-4a13-ac4c-adc581fba50d"
}
]
@@ -69,6 +71,7 @@
Status: "ACTIVE",
GatewayInfo: GatewayInfo{NetworkID: ""},
AdminStateUp: true,
+ Distributed: false,
Name: "second_routers",
ID: "7177abc4-5ae9-4bb7-b0d4-89e94a4abf3b",
TenantID: "6b96ff0cb17a4b859e1e575d221683d3",
@@ -77,6 +80,7 @@
Status: "ACTIVE",
GatewayInfo: GatewayInfo{NetworkID: "3c5bcddd-6af9-4e6b-9c3e-c153e521cab8"},
AdminStateUp: true,
+ Distributed: false,
Name: "router1",
ID: "a9254bdb-2613-4a13-ac4c-adc581fba50d",
TenantID: "33a40233088643acb66ff6eb0ebea679",
@@ -127,6 +131,7 @@
"name": "foo_router",
"admin_state_up": false,
"tenant_id": "6b96ff0cb17a4b859e1e575d221683d3",
+ "distributed": false,
"id": "8604a0de-7f6b-409a-a47c-a1cc7bc77b2e"
}
}
@@ -167,9 +172,16 @@
"external_gateway_info": {
"network_id": "85d76829-6415-48ff-9c63-5c5ca8c61ac6"
},
+ "routes": [
+ {
+ "nexthop": "10.1.0.10",
+ "destination": "40.0.1.0/24"
+ }
+ ],
"name": "router1",
"admin_state_up": true,
"tenant_id": "d6554fe62e2f41efbb6e026fad5c1542",
+ "distributed": false,
"id": "a07eea83-7710-4860-931b-5fe220fae533"
}
}
@@ -185,6 +197,7 @@
th.AssertEquals(t, n.AdminStateUp, true)
th.AssertEquals(t, n.TenantID, "d6554fe62e2f41efbb6e026fad5c1542")
th.AssertEquals(t, n.ID, "a07eea83-7710-4860-931b-5fe220fae533")
+ th.AssertDeepEquals(t, n.Routes, []Route{Route{DestinationCIDR: "40.0.1.0/24", NextHop: "10.1.0.10"}})
}
func TestUpdate(t *testing.T) {
@@ -202,7 +215,13 @@
"name": "new_name",
"external_gateway_info": {
"network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b"
- }
+ },
+ "routes": [
+ {
+ "nexthop": "10.1.0.10",
+ "destination": "40.0.1.0/24"
+ }
+ ]
}
}
`)
@@ -220,20 +239,76 @@
"name": "new_name",
"admin_state_up": true,
"tenant_id": "6b96ff0cb17a4b859e1e575d221683d3",
- "id": "8604a0de-7f6b-409a-a47c-a1cc7bc77b2e"
+ "distributed": false,
+ "id": "8604a0de-7f6b-409a-a47c-a1cc7bc77b2e",
+ "routes": [
+ {
+ "nexthop": "10.1.0.10",
+ "destination": "40.0.1.0/24"
+ }
+ ]
}
}
`)
})
gwi := GatewayInfo{NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b"}
- options := UpdateOpts{Name: "new_name", GatewayInfo: &gwi}
+ r := []Route{Route{DestinationCIDR: "40.0.1.0/24", NextHop: "10.1.0.10"}}
+ options := UpdateOpts{Name: "new_name", GatewayInfo: &gwi, Routes: r}
n, err := Update(fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, n.Name, "new_name")
th.AssertDeepEquals(t, n.GatewayInfo, GatewayInfo{NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b"})
+ th.AssertDeepEquals(t, n.Routes, []Route{Route{DestinationCIDR: "40.0.1.0/24", NextHop: "10.1.0.10"}})
+}
+
+func TestAllRoutesRemoved(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "PUT")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestJSONRequest(t, r, `
+{
+ "router": {
+ "routes": []
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "router": {
+ "status": "ACTIVE",
+ "external_gateway_info": {
+ "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b"
+ },
+ "name": "name",
+ "admin_state_up": true,
+ "tenant_id": "6b96ff0cb17a4b859e1e575d221683d3",
+ "distributed": false,
+ "id": "8604a0de-7f6b-409a-a47c-a1cc7bc77b2e",
+ "routes": []
+ }
+}
+ `)
+ })
+
+ r := []Route{}
+ options := UpdateOpts{Routes: r}
+
+ n, err := Update(fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertDeepEquals(t, n.Routes, []Route{})
}
func TestDelete(t *testing.T) {
diff --git a/openstack/networking/v2/extensions/layer3/routers/results.go b/openstack/networking/v2/extensions/layer3/routers/results.go
old mode 100755
new mode 100644
index bdad4cb..4534123
--- a/openstack/networking/v2/extensions/layer3/routers/results.go
+++ b/openstack/networking/v2/extensions/layer3/routers/results.go
@@ -12,6 +12,11 @@
NetworkID string `json:"network_id" mapstructure:"network_id"`
}
+type Route struct {
+ NextHop string `mapstructure:"nexthop" json:"nexthop"`
+ DestinationCIDR string `mapstructure:"destination" json:"destination"`
+}
+
// Router represents a Neutron router. A router is a logical entity that
// forwards packets across internal subnets and NATs (network address
// translation) them on external networks through an appropriate gateway.
@@ -30,6 +35,9 @@
// Administrative state of the router.
AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"`
+ // Whether router is disitrubted or not..
+ Distributed bool `json:"distributed" mapstructure:"distributed"`
+
// Human readable name for the router. Does not have to be unique.
Name string `json:"name" mapstructure:"name"`
@@ -39,6 +47,8 @@
// Owner of the router. Only admin users can specify a tenant identifier
// other than its own.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
+
+ Routes []Route `json:"routes" mapstructure:"routes"`
}
// RouterPage is the page returned by a pager when traversing over a
diff --git a/openstack/networking/v2/extensions/security/rules/requests.go b/openstack/networking/v2/extensions/security/rules/requests.go
index a80ceb3..e06934a 100644
--- a/openstack/networking/v2/extensions/security/rules/requests.go
+++ b/openstack/networking/v2/extensions/security/rules/requests.go
@@ -104,8 +104,8 @@
TenantID string
}
-// Create is an operation which provisions a new security group with default
-// security group rules for the IPv4 and IPv6 ether types.
+// Create is an operation which adds a new security group rule and associates it
+// with an existing security group (whose ID is specified in CreateOpts).
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
var res CreateResult
@@ -159,14 +159,14 @@
return res
}
-// Get retrieves a particular security group based on its unique ID.
+// Get retrieves a particular security group rule based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = c.Get(resourceURL(c, id), &res.Body, nil)
return res
}
-// Delete will permanently delete a particular security group based on its unique ID.
+// Delete will permanently delete a particular security group rule based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(resourceURL(c, id), nil)
diff --git a/openstack/networking/v2/ports/requests.go b/openstack/networking/v2/ports/requests.go
index 2caf1ca..e73e10a 100644
--- a/openstack/networking/v2/ports/requests.go
+++ b/openstack/networking/v2/ports/requests.go
@@ -95,15 +95,16 @@
// CreateOpts represents the attributes used when creating a new port.
type CreateOpts struct {
- NetworkID string
- Name string
- AdminStateUp *bool
- MACAddress string
- FixedIPs interface{}
- DeviceID string
- DeviceOwner string
- TenantID string
- SecurityGroups []string
+ NetworkID string
+ Name string
+ AdminStateUp *bool
+ MACAddress string
+ FixedIPs interface{}
+ DeviceID string
+ DeviceOwner string
+ TenantID string
+ SecurityGroups []string
+ AllowedAddressPairs []AddressPair
}
// ToPortCreateMap casts a CreateOpts struct to a map.
@@ -139,6 +140,9 @@
if opts.MACAddress != "" {
p["mac_address"] = opts.MACAddress
}
+ if opts.AllowedAddressPairs != nil {
+ p["allowed_address_pairs"] = opts.AllowedAddressPairs
+ }
return map[string]interface{}{"port": p}, nil
}
@@ -168,12 +172,13 @@
// UpdateOpts represents the attributes used when updating an existing port.
type UpdateOpts struct {
- Name string
- AdminStateUp *bool
- FixedIPs interface{}
- DeviceID string
- DeviceOwner string
- SecurityGroups []string
+ Name string
+ AdminStateUp *bool
+ FixedIPs interface{}
+ DeviceID string
+ DeviceOwner string
+ SecurityGroups []string
+ AllowedAddressPairs []AddressPair
}
// ToPortUpdateMap casts an UpdateOpts struct to a map.
@@ -198,6 +203,9 @@
if opts.Name != "" {
p["name"] = opts.Name
}
+ if opts.AllowedAddressPairs != nil {
+ p["allowed_address_pairs"] = opts.AllowedAddressPairs
+ }
return map[string]interface{}{"port": p}, nil
}
diff --git a/openstack/networking/v2/ports/requests_test.go b/openstack/networking/v2/ports/requests_test.go
index 9e323ef..b442996 100644
--- a/openstack/networking/v2/ports/requests_test.go
+++ b/openstack/networking/v2/ports/requests_test.go
@@ -164,7 +164,13 @@
"ip_address": "10.0.0.2"
}
],
- "security_groups": ["foo"]
+ "security_groups": ["foo"],
+ "allowed_address_pairs": [
+ {
+ "ip_address": "10.0.0.4",
+ "mac_address": "fa:16:3e:c9:cb:f0"
+ }
+ ]
}
}
`)
@@ -177,7 +183,6 @@
"port": {
"status": "DOWN",
"name": "private-port",
- "allowed_address_pairs": [],
"admin_state_up": true,
"network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
"tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
@@ -193,6 +198,12 @@
"security_groups": [
"f0ac4394-7e4a-4409-9701-ba8be283dbc3"
],
+ "allowed_address_pairs": [
+ {
+ "ip_address": "10.0.0.4",
+ "mac_address": "fa:16:3e:c9:cb:f0"
+ }
+ ],
"device_id": ""
}
}
@@ -208,6 +219,9 @@
IP{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
},
SecurityGroups: []string{"foo"},
+ AllowedAddressPairs: []AddressPair{
+ AddressPair{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
+ },
}
n, err := Create(fake.ServiceClient(), options).Extract()
th.AssertNoErr(t, err)
@@ -224,6 +238,9 @@
})
th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d")
th.AssertDeepEquals(t, n.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
+ th.AssertDeepEquals(t, n.AllowedAddressPairs, []AddressPair{
+ AddressPair{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
+ })
}
func TestRequiredCreateOpts(t *testing.T) {
@@ -252,6 +269,12 @@
"ip_address": "10.0.0.3"
}
],
+ "allowed_address_pairs": [
+ {
+ "ip_address": "10.0.0.4",
+ "mac_address": "fa:16:3e:c9:cb:f0"
+ }
+ ],
"security_groups": [
"f0ac4394-7e4a-4409-9701-ba8be283dbc3"
]
@@ -278,6 +301,12 @@
"ip_address": "10.0.0.3"
}
],
+ "allowed_address_pairs": [
+ {
+ "ip_address": "10.0.0.4",
+ "mac_address": "fa:16:3e:c9:cb:f0"
+ }
+ ],
"id": "65c0ee9f-d634-4522-8954-51021b570b0d",
"security_groups": [
"f0ac4394-7e4a-4409-9701-ba8be283dbc3"
@@ -294,6 +323,9 @@
IP{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
},
SecurityGroups: []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"},
+ AllowedAddressPairs: []AddressPair{
+ AddressPair{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
+ },
}
s, err := Update(fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract()
@@ -303,6 +335,9 @@
th.AssertDeepEquals(t, s.FixedIPs, []IP{
IP{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
})
+ th.AssertDeepEquals(t, s.AllowedAddressPairs, []AddressPair{
+ AddressPair{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
+ })
th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
}
diff --git a/openstack/networking/v2/ports/results.go b/openstack/networking/v2/ports/results.go
index 2511ff5..1f7eea1 100644
--- a/openstack/networking/v2/ports/results.go
+++ b/openstack/networking/v2/ports/results.go
@@ -19,7 +19,6 @@
var res struct {
Port *Port `json:"port"`
}
-
err := mapstructure.Decode(r.Body, &res)
return res.Port, err
@@ -51,6 +50,11 @@
IPAddress string `mapstructure:"ip_address" json:"ip_address,omitempty"`
}
+type AddressPair struct {
+ IPAddress string `mapstructure:"ip_address" json:"ip_address,omitempty"`
+ MACAddress string `mapstructure:"mac_address" json:"mac_address,omitempty"`
+}
+
// Port represents a Neutron port. See package documentation for a top-level
// description of what this is.
type Port struct {
@@ -78,6 +82,8 @@
SecurityGroups []string `mapstructure:"security_groups" json:"security_groups"`
// Identifies the device (e.g., virtual server) using this port.
DeviceID string `mapstructure:"device_id" json:"device_id"`
+ // Identifies the list of IP addresses the port will recognize/accept
+ AllowedAddressPairs []AddressPair `mapstructure:"allowed_address_pairs" json:"allowed_address_pairs"`
}
// PortPage is the page returned by a pager when traversing over a collection
diff --git a/provider_client.go b/provider_client.go
index 99d9c4a..53fce73 100644
--- a/provider_client.go
+++ b/provider_client.go
@@ -200,6 +200,8 @@
if err != nil {
return nil, fmt.Errorf("Successfully re-authenticated, but got error executing request: %s", err)
}
+
+ return resp, nil
}
}
@@ -247,6 +249,8 @@
return []int{201, 202}
case method == "PUT":
return []int{201, 202}
+ case method == "PATCH":
+ return []int{200, 204}
case method == "DELETE":
return []int{202, 204}
}
@@ -300,6 +304,24 @@
return client.Request("PUT", url, *opts)
}
+func (client *ProviderClient) Patch(url string, JSONBody interface{}, JSONResponse *interface{}, opts *RequestOpts) (*http.Response, error) {
+ if opts == nil {
+ opts = &RequestOpts{}
+ }
+
+ if v, ok := (JSONBody).(io.ReadSeeker); ok {
+ opts.RawBody = v
+ } else if JSONBody != nil {
+ opts.JSONBody = JSONBody
+ }
+
+ if JSONResponse != nil {
+ opts.JSONResponse = JSONResponse
+ }
+
+ return client.Request("PATCH", url, *opts)
+}
+
func (client *ProviderClient) Delete(url string, opts *RequestOpts) (*http.Response, error) {
if opts == nil {
opts = &RequestOpts{}
diff --git a/rackspace/db/v1/backups/results.go b/rackspace/db/v1/backups/results.go
index 82b551d..04faf32 100644
--- a/rackspace/db/v1/backups/results.go
+++ b/rackspace/db/v1/backups/results.go
@@ -1,6 +1,8 @@
package backups
import (
+ "fmt"
+ "reflect"
"time"
"github.com/mitchellh/mapstructure"
@@ -109,12 +111,18 @@
Backups []Backup `mapstructure:"backups" json:"backups"`
}
- err := mapstructure.Decode(casted, &resp)
+ if err := mapstructure.Decode(casted, &resp); err != nil {
+ return nil, err
+ }
var vals []interface{}
- switch (casted).(type) {
- case interface{}:
+ switch casted.(type) {
+ case map[string]interface{}:
vals = casted.(map[string]interface{})["backups"].([]interface{})
+ case map[string][]interface{}:
+ vals = casted.(map[string][]interface{})["backups"]
+ default:
+ return resp.Backups, fmt.Errorf("Unknown type: %v", reflect.TypeOf(casted))
}
for i, v := range vals {
@@ -137,5 +145,5 @@
}
}
- return resp.Backups, err
+ return resp.Backups, nil
}
diff --git a/rackspace/db/v1/instances/results.go b/rackspace/db/v1/instances/results.go
index 4b1317e..cdcc9c7 100644
--- a/rackspace/db/v1/instances/results.go
+++ b/rackspace/db/v1/instances/results.go
@@ -1,6 +1,8 @@
package instances
import (
+ "fmt"
+ "reflect"
"time"
"github.com/mitchellh/mapstructure"
@@ -147,16 +149,22 @@
func ExtractInstances(page pagination.Page) ([]Instance, error) {
casted := page.(os.InstancePage).Body
- var response struct {
+ var resp struct {
Instances []Instance `mapstructure:"instances"`
}
- err := mapstructure.Decode(casted, &response)
+ if err := mapstructure.Decode(casted, &resp); err != nil {
+ return nil, err
+ }
var vals []interface{}
- switch (casted).(type) {
- case interface{}:
+ switch casted.(type) {
+ case map[string]interface{}:
vals = casted.(map[string]interface{})["instances"].([]interface{})
+ case map[string][]interface{}:
+ vals = casted.(map[string][]interface{})["instances"]
+ default:
+ return resp.Instances, fmt.Errorf("Unknown type: %v", reflect.TypeOf(casted))
}
for i, v := range vals {
@@ -165,19 +173,19 @@
if t, ok := val["created"].(string); ok && t != "" {
creationTime, err := time.Parse(time.RFC3339, t)
if err != nil {
- return response.Instances, err
+ return resp.Instances, err
}
- response.Instances[i].Created = creationTime
+ resp.Instances[i].Created = creationTime
}
if t, ok := val["updated"].(string); ok && t != "" {
updatedTime, err := time.Parse(time.RFC3339, t)
if err != nil {
- return response.Instances, err
+ return resp.Instances, err
}
- response.Instances[i].Updated = updatedTime
+ resp.Instances[i].Updated = updatedTime
}
}
- return response.Instances, err
+ return resp.Instances, nil
}
diff --git a/rackspace/lb/v1/nodes/requests.go b/rackspace/lb/v1/nodes/requests.go
index dc2d46c..9da376f 100644
--- a/rackspace/lb/v1/nodes/requests.go
+++ b/rackspace/lb/v1/nodes/requests.go
@@ -249,3 +249,38 @@
return NodeEventPage{pagination.SinglePageBase(r)}
})
}
+
+// GetByIPPort locates a load balancer node by IP and port.
+func GetByIPPort(
+ client *gophercloud.ServiceClient,
+ loadBalancerID int,
+ address string,
+ port int,
+) (*Node, error) {
+
+ // nil until found
+ var found *Node
+
+ List(client, loadBalancerID, nil).EachPage(func(page pagination.Page) (bool, error) {
+ lbNodes, err := ExtractNodes(page)
+ if err != nil {
+ return false, err
+ }
+
+ for _, trialNode := range lbNodes {
+ if trialNode.Address == address && trialNode.Port == port {
+ found = &trialNode
+ return false, nil
+ }
+
+ }
+
+ return true, nil
+ })
+
+ if found == nil {
+ return nil, errors.New("Unable to get node by IP and Port")
+ }
+
+ return found, nil
+}