updated gophercloud
This commit is contained in:
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
@@ -489,8 +489,8 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud",
|
||||
"Comment": "v1.0.0-569-gf3ced00",
|
||||
"Rev": "f3ced00552c1c7d4a6184500af9062cfb4ff4463"
|
||||
"Comment": "v1.0.0-665-gf928634",
|
||||
"Rev": "f92863476c034f851073599c09d90cd61ee95b3d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/russross/blackfriday",
|
||||
|
||||
5
Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTING.md
generated
vendored
5
Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTING.md
generated
vendored
@@ -28,11 +28,10 @@ fork as `origin` instead:
|
||||
git remote add origin git@github.com/<my_username>/gophercloud
|
||||
```
|
||||
|
||||
4. Checkout the latest development branch ([click here](/branches) to see all
|
||||
the branches):
|
||||
4. Checkout the latest development branch:
|
||||
|
||||
```bash
|
||||
git checkout release/v1.0.1
|
||||
git checkout master
|
||||
```
|
||||
|
||||
5. If you're working on something (discussed more in detail below), you will
|
||||
|
||||
78
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/network_test.go
generated
vendored
Normal file
78
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/compute/v2/network_test.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
// +build acceptance compute servers
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
)
|
||||
|
||||
func getNetworkIDFromNetworkExtension(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
|
||||
allPages, err := networks.List(client).AllPages()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to list networks: %v", err)
|
||||
}
|
||||
|
||||
networkList, err := networks.ExtractNetworks(allPages)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to list networks: %v", err)
|
||||
}
|
||||
|
||||
networkID := ""
|
||||
for _, network := range networkList {
|
||||
t.Logf("Network: %v", network)
|
||||
if network.Label == networkName {
|
||||
networkID = network.ID
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Found network ID for %s: %s\n", networkName, networkID)
|
||||
|
||||
return networkID, nil
|
||||
}
|
||||
|
||||
func TestNetworks(t *testing.T) {
|
||||
networkName := os.Getenv("OS_NETWORK_NAME")
|
||||
if networkName == "" {
|
||||
t.Fatalf("OS_NETWORK_NAME must be set")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
networkID, err := getNetworkIDFromNetworkExtension(t, client, networkName)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to get network ID: %v", err)
|
||||
}
|
||||
|
||||
// createNetworkServer is defined in tenantnetworks_test.go
|
||||
server, err := createNetworkServer(t, client, choices, networkID)
|
||||
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)
|
||||
}
|
||||
|
||||
allPages, err := networks.List(client).AllPages()
|
||||
allNetworks, err := networks.ExtractNetworks(allPages)
|
||||
th.AssertNoErr(t, err)
|
||||
t.Logf("Retrieved all %d networks: %+v", len(allNetworks), allNetworks)
|
||||
}
|
||||
@@ -3,10 +3,15 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/acceptance/tools"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/servergroups"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
)
|
||||
|
||||
func createServerGroup(t *testing.T, computeClient *gophercloud.ServiceClient) (*servergroups.ServerGroup, error) {
|
||||
@@ -36,7 +41,53 @@ func getServerGroup(t *testing.T, computeClient *gophercloud.ServiceClient, sgID
|
||||
return nil
|
||||
}
|
||||
|
||||
func createServerInGroup(t *testing.T, computeClient *gophercloud.ServiceClient, choices *ComputeChoices, serverGroup *servergroups.ServerGroup) (*servers.Server, error) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping test that requires server creation in short mode.")
|
||||
}
|
||||
|
||||
name := tools.RandomString("ACPTTEST", 16)
|
||||
t.Logf("Attempting to create server: %s\n", name)
|
||||
|
||||
pwd := tools.MakeNewPassword("")
|
||||
|
||||
serverCreateOpts := servers.CreateOpts{
|
||||
Name: name,
|
||||
FlavorRef: choices.FlavorID,
|
||||
ImageRef: choices.ImageID,
|
||||
AdminPass: pwd,
|
||||
}
|
||||
server, err := servers.Create(computeClient, schedulerhints.CreateOptsExt{
|
||||
serverCreateOpts,
|
||||
schedulerhints.SchedulerHints{
|
||||
Group: serverGroup.ID,
|
||||
},
|
||||
}).Extract()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create server: %v", err)
|
||||
}
|
||||
|
||||
th.AssertEquals(t, pwd, server.AdminPass)
|
||||
|
||||
return server, err
|
||||
}
|
||||
|
||||
func verifySchedulerWorked(t *testing.T, firstServer, secondServer *servers.Server) error {
|
||||
t.Logf("First server hostID: %v", firstServer.HostID)
|
||||
t.Logf("Second server hostID: %v", secondServer.HostID)
|
||||
if firstServer.HostID == secondServer.HostID {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s and %s were not scheduled on the same host.", firstServer.ID, secondServer.ID)
|
||||
}
|
||||
|
||||
func TestServerGroups(t *testing.T) {
|
||||
choices, err := ComputeChoicesFromEnv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
computeClient, err := newClient()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create a compute client: %v", err)
|
||||
@@ -55,4 +106,38 @@ func TestServerGroups(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to get server group: %v", err)
|
||||
}
|
||||
|
||||
firstServer, err := createServerInGroup(t, computeClient, choices, sg)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create server: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
servers.Delete(computeClient, firstServer.ID)
|
||||
t.Logf("Server deleted.")
|
||||
}()
|
||||
|
||||
if err = waitForStatus(computeClient, firstServer, "ACTIVE"); err != nil {
|
||||
t.Fatalf("Unable to wait for server: %v", err)
|
||||
}
|
||||
|
||||
firstServer, err = servers.Get(computeClient, firstServer.ID).Extract()
|
||||
|
||||
secondServer, err := createServerInGroup(t, computeClient, choices, sg)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create server: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
servers.Delete(computeClient, secondServer.ID)
|
||||
t.Logf("Server deleted.")
|
||||
}()
|
||||
|
||||
if err = waitForStatus(computeClient, secondServer, "ACTIVE"); err != nil {
|
||||
t.Fatalf("Unable to wait for server: %v", err)
|
||||
}
|
||||
|
||||
secondServer, err = servers.Get(computeClient, secondServer.ID).Extract()
|
||||
|
||||
if err = verifySchedulerWorked(t, firstServer, secondServer); err != nil {
|
||||
t.Fatalf("Scheduling did not work: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,12 @@ func createServer(t *testing.T, client *gophercloud.ServiceClient, choices *Comp
|
||||
servers.Network{UUID: network.ID},
|
||||
},
|
||||
AdminPass: pwd,
|
||||
Personality: servers.Personality{
|
||||
&servers.File{
|
||||
Path: "/etc/test",
|
||||
Contents: []byte("hello world"),
|
||||
},
|
||||
},
|
||||
}).Extract()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create server: %v", err)
|
||||
|
||||
61
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/tokens_test.go
generated
vendored
Normal file
61
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/identity/v2/tokens_test.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// +build acceptance
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/acceptance/tools"
|
||||
"github.com/rackspace/gophercloud/rackspace"
|
||||
"github.com/rackspace/gophercloud/rackspace/identity/v2/tokens"
|
||||
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 TestTokenAuth(t *testing.T) {
|
||||
authedClient := createClient(t, true)
|
||||
token := authedClient.TokenID
|
||||
|
||||
tenantID := os.Getenv("RS_TENANT_ID")
|
||||
if tenantID == "" {
|
||||
t.Skip("You must set RS_TENANT_ID environment variable to run this test")
|
||||
}
|
||||
|
||||
authOpts := tokens.AuthOptions{}
|
||||
authOpts.TenantID = tenantID
|
||||
authOpts.Token = token
|
||||
|
||||
_, err := tokens.Create(authedClient, authOpts).ExtractToken()
|
||||
th.AssertNoErr(t, err)
|
||||
}
|
||||
4
Godeps/_workspace/src/github.com/rackspace/gophercloud/auth_options.go
generated
vendored
4
Godeps/_workspace/src/github.com/rackspace/gophercloud/auth_options.go
generated
vendored
@@ -43,4 +43,8 @@ type AuthOptions struct {
|
||||
// false, it will not cache these settings, but re-authentication will not be
|
||||
// possible. This setting defaults to false.
|
||||
AllowReauth bool
|
||||
|
||||
// TokenID allows users to authenticate (possibly as another user) with an
|
||||
// authentication token ID.
|
||||
TokenID string
|
||||
}
|
||||
|
||||
@@ -171,3 +171,36 @@ func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMet
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// IDFromName is a convienience function that returns a snapshot's ID given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
snapshotCount := 0
|
||||
snapshotID := ""
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("A snapshot name must be provided.")
|
||||
}
|
||||
pager := List(client, nil)
|
||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
snapshotList, err := ExtractSnapshots(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, s := range snapshotList {
|
||||
if s.Name == name {
|
||||
snapshotCount++
|
||||
snapshotID = s.ID
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
switch snapshotCount {
|
||||
case 0:
|
||||
return "", fmt.Errorf("Unable to find snapshot: %s", name)
|
||||
case 1:
|
||||
return snapshotID, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Found %d snapshots matching %s", snapshotCount, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,3 +201,36 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// IDFromName is a convienience function that returns a server's ID given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
volumeCount := 0
|
||||
volumeID := ""
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("A volume name must be provided.")
|
||||
}
|
||||
pager := List(client, nil)
|
||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
volumeList, err := ExtractVolumes(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, s := range volumeList {
|
||||
if s.Name == name {
|
||||
volumeCount++
|
||||
volumeID = s.ID
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
switch volumeCount {
|
||||
case 0:
|
||||
return "", fmt.Errorf("Unable to find volume: %s", name)
|
||||
case 1:
|
||||
return volumeID, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Found %d volumes matching %s", volumeCount, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package volumes
|
||||
import (
|
||||
"testing"
|
||||
|
||||
fixtures "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/testing"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
"github.com/rackspace/gophercloud/testhelper/client"
|
||||
@@ -12,7 +13,7 @@ func TestList(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
|
||||
MockListResponse(t)
|
||||
fixtures.MockListResponse(t)
|
||||
|
||||
count := 0
|
||||
|
||||
@@ -49,7 +50,7 @@ func TestListAll(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
|
||||
MockListResponse(t)
|
||||
fixtures.MockListResponse(t)
|
||||
|
||||
allPages, err := List(client.ServiceClient(), &ListOpts{}).AllPages()
|
||||
th.AssertNoErr(t, err)
|
||||
@@ -75,7 +76,7 @@ func TestGet(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
|
||||
MockGetResponse(t)
|
||||
fixtures.MockGetResponse(t)
|
||||
|
||||
v, err := Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract()
|
||||
th.AssertNoErr(t, err)
|
||||
@@ -89,7 +90,7 @@ func TestCreate(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
|
||||
MockCreateResponse(t)
|
||||
fixtures.MockCreateResponse(t)
|
||||
|
||||
options := &CreateOpts{Size: 75}
|
||||
n, err := Create(client.ServiceClient(), options).Extract()
|
||||
@@ -103,7 +104,7 @@ func TestDelete(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
|
||||
MockDeleteResponse(t)
|
||||
fixtures.MockDeleteResponse(t)
|
||||
|
||||
res := Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22")
|
||||
th.AssertNoErr(t, res.Err)
|
||||
@@ -113,7 +114,7 @@ func TestUpdate(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
|
||||
MockUpdateResponse(t)
|
||||
fixtures.MockUpdateResponse(t)
|
||||
|
||||
options := UpdateOpts{Name: "vol-002"}
|
||||
v, err := Update(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract()
|
||||
|
||||
7
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/testing/doc.go
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/testing/doc.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
This is package created is to hold fixtures (which imports testing),
|
||||
so that importing volumes package does not inadvertently import testing into production code
|
||||
More information here:
|
||||
https://github.com/rackspace/gophercloud/issues/473
|
||||
*/
|
||||
package testing
|
||||
@@ -1,4 +1,4 @@
|
||||
package volumes
|
||||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -32,6 +32,8 @@ func TestCreateOpts(t *testing.T) {
|
||||
"name": "createdserver",
|
||||
"imageRef": "asdfasdfasdf",
|
||||
"flavorRef": "performance1-1",
|
||||
"flavorName": "",
|
||||
"imageName": "",
|
||||
"block_device_mapping_v2":[
|
||||
{
|
||||
"uuid":"123456",
|
||||
|
||||
@@ -25,6 +25,8 @@ func TestCreateOpts(t *testing.T) {
|
||||
"name": "createdserver",
|
||||
"imageRef": "asdfasdfasdf",
|
||||
"flavorRef": "performance1-1",
|
||||
"flavorName": "",
|
||||
"imageName": "",
|
||||
"OS-DCF:diskConfig": "MANUAL"
|
||||
}
|
||||
}
|
||||
|
||||
2
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/doc.go
generated
vendored
Normal file
2
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/doc.go
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package network provides the ability to manage nova-networks
|
||||
package networks
|
||||
209
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/fixtures.go
generated
vendored
Normal file
209
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/fixtures.go
generated
vendored
Normal file
@@ -0,0 +1,209 @@
|
||||
// +build fixtures
|
||||
|
||||
package networks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
"github.com/rackspace/gophercloud/testhelper/client"
|
||||
)
|
||||
|
||||
// ListOutput is a sample response to a List call.
|
||||
const ListOutput = `
|
||||
{
|
||||
"networks": [
|
||||
{
|
||||
"bridge": "br100",
|
||||
"bridge_interface": "eth0",
|
||||
"broadcast": "10.0.0.7",
|
||||
"cidr": "10.0.0.0/29",
|
||||
"cidr_v6": null,
|
||||
"created_at": "2011-08-15 06:19:19.387525",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"dhcp_start": "10.0.0.3",
|
||||
"dns1": null,
|
||||
"dns2": null,
|
||||
"gateway": "10.0.0.1",
|
||||
"gateway_v6": null,
|
||||
"host": "nsokolov-desktop",
|
||||
"id": "20c8acc0-f747-4d71-a389-46d078ebf047",
|
||||
"injected": false,
|
||||
"label": "mynet_0",
|
||||
"multi_host": false,
|
||||
"netmask": "255.255.255.248",
|
||||
"netmask_v6": null,
|
||||
"priority": null,
|
||||
"project_id": "1234",
|
||||
"rxtx_base": null,
|
||||
"updated_at": "2011-08-16 09:26:13.048257",
|
||||
"vlan": 100,
|
||||
"vpn_private_address": "10.0.0.2",
|
||||
"vpn_public_address": "127.0.0.1",
|
||||
"vpn_public_port": 1000
|
||||
},
|
||||
{
|
||||
"bridge": "br101",
|
||||
"bridge_interface": "eth0",
|
||||
"broadcast": "10.0.0.15",
|
||||
"cidr": "10.0.0.10/29",
|
||||
"cidr_v6": null,
|
||||
"created_at": "2011-08-15 06:19:19.885495",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"dhcp_start": "10.0.0.11",
|
||||
"dns1": null,
|
||||
"dns2": null,
|
||||
"gateway": "10.0.0.9",
|
||||
"gateway_v6": null,
|
||||
"host": null,
|
||||
"id": "20c8acc0-f747-4d71-a389-46d078ebf000",
|
||||
"injected": false,
|
||||
"label": "mynet_1",
|
||||
"multi_host": false,
|
||||
"netmask": "255.255.255.248",
|
||||
"netmask_v6": null,
|
||||
"priority": null,
|
||||
"project_id": null,
|
||||
"rxtx_base": null,
|
||||
"updated_at": null,
|
||||
"vlan": 101,
|
||||
"vpn_private_address": "10.0.0.10",
|
||||
"vpn_public_address": null,
|
||||
"vpn_public_port": 1001
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
// GetOutput is a sample response to a Get call.
|
||||
const GetOutput = `
|
||||
{
|
||||
"network": {
|
||||
"bridge": "br101",
|
||||
"bridge_interface": "eth0",
|
||||
"broadcast": "10.0.0.15",
|
||||
"cidr": "10.0.0.10/29",
|
||||
"cidr_v6": null,
|
||||
"created_at": "2011-08-15 06:19:19.885495",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"dhcp_start": "10.0.0.11",
|
||||
"dns1": null,
|
||||
"dns2": null,
|
||||
"gateway": "10.0.0.9",
|
||||
"gateway_v6": null,
|
||||
"host": null,
|
||||
"id": "20c8acc0-f747-4d71-a389-46d078ebf000",
|
||||
"injected": false,
|
||||
"label": "mynet_1",
|
||||
"multi_host": false,
|
||||
"netmask": "255.255.255.248",
|
||||
"netmask_v6": null,
|
||||
"priority": null,
|
||||
"project_id": null,
|
||||
"rxtx_base": null,
|
||||
"updated_at": null,
|
||||
"vlan": 101,
|
||||
"vpn_private_address": "10.0.0.10",
|
||||
"vpn_public_address": null,
|
||||
"vpn_public_port": 1001
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// FirstNetwork is the first result in ListOutput.
|
||||
var nilTime time.Time
|
||||
var FirstNetwork = Network{
|
||||
Bridge: "br100",
|
||||
BridgeInterface: "eth0",
|
||||
Broadcast: "10.0.0.7",
|
||||
CIDR: "10.0.0.0/29",
|
||||
CIDRv6: "",
|
||||
CreatedAt: time.Date(2011, 8, 15, 6, 19, 19, 387525000, time.UTC),
|
||||
Deleted: false,
|
||||
DeletedAt: nilTime,
|
||||
DHCPStart: "10.0.0.3",
|
||||
DNS1: "",
|
||||
DNS2: "",
|
||||
Gateway: "10.0.0.1",
|
||||
Gatewayv6: "",
|
||||
Host: "nsokolov-desktop",
|
||||
ID: "20c8acc0-f747-4d71-a389-46d078ebf047",
|
||||
Injected: false,
|
||||
Label: "mynet_0",
|
||||
MultiHost: false,
|
||||
Netmask: "255.255.255.248",
|
||||
Netmaskv6: "",
|
||||
Priority: 0,
|
||||
ProjectID: "1234",
|
||||
RXTXBase: 0,
|
||||
UpdatedAt: time.Date(2011, 8, 16, 9, 26, 13, 48257000, time.UTC),
|
||||
VLAN: 100,
|
||||
VPNPrivateAddress: "10.0.0.2",
|
||||
VPNPublicAddress: "127.0.0.1",
|
||||
VPNPublicPort: 1000,
|
||||
}
|
||||
|
||||
// SecondNetwork is the second result in ListOutput.
|
||||
var SecondNetwork = Network{
|
||||
Bridge: "br101",
|
||||
BridgeInterface: "eth0",
|
||||
Broadcast: "10.0.0.15",
|
||||
CIDR: "10.0.0.10/29",
|
||||
CIDRv6: "",
|
||||
CreatedAt: time.Date(2011, 8, 15, 6, 19, 19, 885495000, time.UTC),
|
||||
Deleted: false,
|
||||
DeletedAt: nilTime,
|
||||
DHCPStart: "10.0.0.11",
|
||||
DNS1: "",
|
||||
DNS2: "",
|
||||
Gateway: "10.0.0.9",
|
||||
Gatewayv6: "",
|
||||
Host: "",
|
||||
ID: "20c8acc0-f747-4d71-a389-46d078ebf000",
|
||||
Injected: false,
|
||||
Label: "mynet_1",
|
||||
MultiHost: false,
|
||||
Netmask: "255.255.255.248",
|
||||
Netmaskv6: "",
|
||||
Priority: 0,
|
||||
ProjectID: "",
|
||||
RXTXBase: 0,
|
||||
UpdatedAt: nilTime,
|
||||
VLAN: 101,
|
||||
VPNPrivateAddress: "10.0.0.10",
|
||||
VPNPublicAddress: "",
|
||||
VPNPublicPort: 1001,
|
||||
}
|
||||
|
||||
// ExpectedNetworkSlice is the slice of results that should be parsed
|
||||
// from ListOutput, in the expected order.
|
||||
var ExpectedNetworkSlice = []Network{FirstNetwork, SecondNetwork}
|
||||
|
||||
// HandleListSuccessfully configures the test server to respond to a List request.
|
||||
func HandleListSuccessfully(t *testing.T) {
|
||||
th.Mux.HandleFunc("/os-networks", func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "GET")
|
||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, ListOutput)
|
||||
})
|
||||
}
|
||||
|
||||
// HandleGetSuccessfully configures the test server to respond to a Get request
|
||||
// for an existing network.
|
||||
func HandleGetSuccessfully(t *testing.T) {
|
||||
th.Mux.HandleFunc("/os-networks/20c8acc0-f747-4d71-a389-46d078ebf000", func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "GET")
|
||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, GetOutput)
|
||||
})
|
||||
}
|
||||
22
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/requests.go
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/requests.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package networks
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// List returns a Pager that allows you to iterate over a collection of Network.
|
||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
||||
url := listURL(client)
|
||||
createPage := func(r pagination.PageResult) pagination.Page {
|
||||
return NetworkPage{pagination.SinglePageBase(r)}
|
||||
}
|
||||
return pagination.NewPager(client, url, createPage)
|
||||
}
|
||||
|
||||
// Get returns data about a previously created Network.
|
||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
||||
var res GetResult
|
||||
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
|
||||
return res
|
||||
}
|
||||
37
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/requests_test.go
generated
vendored
Normal file
37
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/requests_test.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package networks
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
"github.com/rackspace/gophercloud/testhelper/client"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
HandleListSuccessfully(t)
|
||||
|
||||
count := 0
|
||||
err := List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
|
||||
count++
|
||||
actual, err := ExtractNetworks(page)
|
||||
th.AssertNoErr(t, err)
|
||||
th.CheckDeepEquals(t, ExpectedNetworkSlice, actual)
|
||||
|
||||
return true, nil
|
||||
})
|
||||
th.AssertNoErr(t, err)
|
||||
th.CheckEquals(t, 1, count)
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
HandleGetSuccessfully(t)
|
||||
|
||||
actual, err := Get(client.ServiceClient(), "20c8acc0-f747-4d71-a389-46d078ebf000").Extract()
|
||||
th.AssertNoErr(t, err)
|
||||
th.CheckDeepEquals(t, &SecondNetwork, actual)
|
||||
}
|
||||
222
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/results.go
generated
vendored
Normal file
222
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/results.go
generated
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
package networks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// A Network represents a nova-network that an instance communicates on
|
||||
type Network struct {
|
||||
// The Bridge that VIFs on this network are connected to
|
||||
Bridge string `mapstructure:"bridge"`
|
||||
|
||||
// BridgeInterface is what interface is connected to the Bridge
|
||||
BridgeInterface string `mapstructure:"bridge_interface"`
|
||||
|
||||
// The Broadcast address of the network.
|
||||
Broadcast string `mapstructure:"broadcast"`
|
||||
|
||||
// CIDR is the IPv4 subnet.
|
||||
CIDR string `mapstructure:"cidr"`
|
||||
|
||||
// CIDRv6 is the IPv6 subnet.
|
||||
CIDRv6 string `mapstructure:"cidr_v6"`
|
||||
|
||||
// CreatedAt is when the network was created..
|
||||
CreatedAt time.Time `mapstructure:"-"`
|
||||
|
||||
// Deleted shows if the network has been deleted.
|
||||
Deleted bool `mapstructure:"deleted"`
|
||||
|
||||
// DeletedAt is the time when the network was deleted.
|
||||
DeletedAt time.Time `mapstructure:"-"`
|
||||
|
||||
// DHCPStart is the start of the DHCP address range.
|
||||
DHCPStart string `mapstructure:"dhcp_start"`
|
||||
|
||||
// DNS1 is the first DNS server to use through DHCP.
|
||||
DNS1 string `mapstructure:"dns_1"`
|
||||
|
||||
// DNS2 is the first DNS server to use through DHCP.
|
||||
DNS2 string `mapstructure:"dns_2"`
|
||||
|
||||
// Gateway is the network gateway.
|
||||
Gateway string `mapstructure:"gateway"`
|
||||
|
||||
// Gatewayv6 is the IPv6 network gateway.
|
||||
Gatewayv6 string `mapstructure:"gateway_v6"`
|
||||
|
||||
// Host is the host that the network service is running on.
|
||||
Host string `mapstructure:"host"`
|
||||
|
||||
// ID is the UUID of the network.
|
||||
ID string `mapstructure:"id"`
|
||||
|
||||
// Injected determines if network information is injected into the host.
|
||||
Injected bool `mapstructure:"injected"`
|
||||
|
||||
// Label is the common name that the network has..
|
||||
Label string `mapstructure:"label"`
|
||||
|
||||
// MultiHost is if multi-host networking is enablec..
|
||||
MultiHost bool `mapstructure:"multi_host"`
|
||||
|
||||
// Netmask is the network netmask.
|
||||
Netmask string `mapstructure:"netmask"`
|
||||
|
||||
// Netmaskv6 is the IPv6 netmask.
|
||||
Netmaskv6 string `mapstructure:"netmask_v6"`
|
||||
|
||||
// Priority is the network interface priority.
|
||||
Priority int `mapstructure:"priority"`
|
||||
|
||||
// ProjectID is the project associated with this network.
|
||||
ProjectID string `mapstructure:"project_id"`
|
||||
|
||||
// RXTXBase configures bandwidth entitlement.
|
||||
RXTXBase int `mapstructure:"rxtx_base"`
|
||||
|
||||
// UpdatedAt is the time when the network was last updated.
|
||||
UpdatedAt time.Time `mapstructure:"-"`
|
||||
|
||||
// VLAN is the vlan this network runs on.
|
||||
VLAN int `mapstructure:"vlan"`
|
||||
|
||||
// VPNPrivateAddress is the private address of the CloudPipe VPN.
|
||||
VPNPrivateAddress string `mapstructure:"vpn_private_address"`
|
||||
|
||||
// VPNPublicAddress is the public address of the CloudPipe VPN.
|
||||
VPNPublicAddress string `mapstructure:"vpn_public_address"`
|
||||
|
||||
// VPNPublicPort is the port of the CloudPipe VPN.
|
||||
VPNPublicPort int `mapstructure:"vpn_public_port"`
|
||||
}
|
||||
|
||||
// NetworkPage stores a single, only page of Networks
|
||||
// results from a List call.
|
||||
type NetworkPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty determines whether or not a NetworkPage is empty.
|
||||
func (page NetworkPage) IsEmpty() (bool, error) {
|
||||
va, err := ExtractNetworks(page)
|
||||
return len(va) == 0, err
|
||||
}
|
||||
|
||||
// ExtractNetworks interprets a page of results as a slice of Networks
|
||||
func ExtractNetworks(page pagination.Page) ([]Network, error) {
|
||||
var res struct {
|
||||
Networks []Network `mapstructure:"networks"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(page.(NetworkPage).Body, &res)
|
||||
|
||||
var rawNetworks []interface{}
|
||||
body := page.(NetworkPage).Body
|
||||
switch body.(type) {
|
||||
case map[string]interface{}:
|
||||
rawNetworks = body.(map[string]interface{})["networks"].([]interface{})
|
||||
case map[string][]interface{}:
|
||||
rawNetworks = body.(map[string][]interface{})["networks"]
|
||||
default:
|
||||
return res.Networks, fmt.Errorf("Unknown type")
|
||||
}
|
||||
|
||||
for i := range rawNetworks {
|
||||
thisNetwork := rawNetworks[i].(map[string]interface{})
|
||||
if t, ok := thisNetwork["created_at"].(string); ok && t != "" {
|
||||
createdAt, err := time.Parse("2006-01-02 15:04:05.000000", t)
|
||||
if err != nil {
|
||||
return res.Networks, err
|
||||
}
|
||||
res.Networks[i].CreatedAt = createdAt
|
||||
}
|
||||
|
||||
if t, ok := thisNetwork["updated_at"].(string); ok && t != "" {
|
||||
updatedAt, err := time.Parse("2006-01-02 15:04:05.000000", t)
|
||||
if err != nil {
|
||||
return res.Networks, err
|
||||
}
|
||||
res.Networks[i].UpdatedAt = updatedAt
|
||||
}
|
||||
|
||||
if t, ok := thisNetwork["deleted_at"].(string); ok && t != "" {
|
||||
deletedAt, err := time.Parse("2006-01-02 15:04:05.000000", t)
|
||||
if err != nil {
|
||||
return res.Networks, err
|
||||
}
|
||||
res.Networks[i].DeletedAt = deletedAt
|
||||
}
|
||||
}
|
||||
|
||||
return res.Networks, err
|
||||
}
|
||||
|
||||
type NetworkResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract is a method that attempts to interpret any Network resource
|
||||
// response as a Network struct.
|
||||
func (r NetworkResult) Extract() (*Network, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
var res struct {
|
||||
Network *Network `json:"network" mapstructure:"network"`
|
||||
}
|
||||
|
||||
config := &mapstructure.DecoderConfig{
|
||||
Result: &res,
|
||||
WeaklyTypedInput: true,
|
||||
}
|
||||
decoder, err := mapstructure.NewDecoder(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := decoder.Decode(r.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b := r.Body.(map[string]interface{})["network"].(map[string]interface{})
|
||||
|
||||
if t, ok := b["created_at"].(string); ok && t != "" {
|
||||
createdAt, err := time.Parse("2006-01-02 15:04:05.000000", t)
|
||||
if err != nil {
|
||||
return res.Network, err
|
||||
}
|
||||
res.Network.CreatedAt = createdAt
|
||||
}
|
||||
|
||||
if t, ok := b["updated_at"].(string); ok && t != "" {
|
||||
updatedAt, err := time.Parse("2006-01-02 15:04:05.000000", t)
|
||||
if err != nil {
|
||||
return res.Network, err
|
||||
}
|
||||
res.Network.UpdatedAt = updatedAt
|
||||
}
|
||||
|
||||
if t, ok := b["deleted_at"].(string); ok && t != "" {
|
||||
deletedAt, err := time.Parse("2006-01-02 15:04:05.000000", t)
|
||||
if err != nil {
|
||||
return res.Network, err
|
||||
}
|
||||
res.Network.DeletedAt = deletedAt
|
||||
}
|
||||
|
||||
return res.Network, err
|
||||
|
||||
}
|
||||
|
||||
// GetResult is the response from a Get operation. Call its Extract method to interpret it
|
||||
// as a Network.
|
||||
type GetResult struct {
|
||||
NetworkResult
|
||||
}
|
||||
17
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/urls.go
generated
vendored
Normal file
17
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/urls.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
package networks
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
|
||||
const resourcePath = "os-networks"
|
||||
|
||||
func resourceURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL(resourcePath)
|
||||
}
|
||||
|
||||
func listURL(c *gophercloud.ServiceClient) string {
|
||||
return resourceURL(c)
|
||||
}
|
||||
|
||||
func getURL(c *gophercloud.ServiceClient, id string) string {
|
||||
return c.ServiceURL(resourcePath, id)
|
||||
}
|
||||
25
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/urls_test.go
generated
vendored
Normal file
25
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/networks/urls_test.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package networks
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
"github.com/rackspace/gophercloud/testhelper/client"
|
||||
)
|
||||
|
||||
func TestListURL(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
c := client.ServiceClient()
|
||||
|
||||
th.CheckEquals(t, c.Endpoint+"os-networks", listURL(c))
|
||||
}
|
||||
|
||||
func TestGetURL(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
c := client.ServiceClient()
|
||||
id := "1"
|
||||
|
||||
th.CheckEquals(t, c.Endpoint+"os-networks/"+id, getURL(c, id))
|
||||
}
|
||||
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints/doc.go
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints/doc.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package schedulerhints enables instances to provide the OpenStack scheduler
|
||||
// hints about where they should be placed in the cloud.
|
||||
package schedulerhints
|
||||
134
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go
generated
vendored
Normal file
134
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
package schedulerhints
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
)
|
||||
|
||||
// SchedulerHints represents a set of scheduling hints that are passed to the
|
||||
// OpenStack scheduler
|
||||
type SchedulerHints struct {
|
||||
// Group specifies a Server Group to place the instance in.
|
||||
Group string
|
||||
|
||||
// DifferentHost will place the instance on a compute node that does not
|
||||
// host the given instances.
|
||||
DifferentHost []string
|
||||
|
||||
// SameHost will place the instance on a compute node that hosts the given
|
||||
// instances.
|
||||
SameHost []string
|
||||
|
||||
// Query is a conditional statement that results in compute nodes able to
|
||||
// host the instance.
|
||||
Query []interface{}
|
||||
|
||||
// TargetCell specifies a cell name where the instance will be placed.
|
||||
TargetCell string
|
||||
|
||||
// BuildNearHostIP specifies a subnet of compute nodes to host the instance.
|
||||
BuildNearHostIP string
|
||||
}
|
||||
|
||||
// SchedulerHintsBuilder builds the scheduler hints into a serializable format.
|
||||
type SchedulerHintsBuilder interface {
|
||||
ToServerSchedulerHintsMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// ToServerSchedulerHintsMap builds the scheduler hints into a serializable format.
|
||||
func (opts SchedulerHints) ToServerSchedulerHintsMap() (map[string]interface{}, error) {
|
||||
sh := make(map[string]interface{})
|
||||
|
||||
uuidRegex, _ := regexp.Compile("^[a-z0-9]{8}-[a-z0-9]{4}-[1-5][a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12}$")
|
||||
|
||||
if opts.Group != "" {
|
||||
if !uuidRegex.MatchString(opts.Group) {
|
||||
return nil, fmt.Errorf("Group must be a UUID")
|
||||
}
|
||||
sh["group"] = opts.Group
|
||||
}
|
||||
|
||||
if len(opts.DifferentHost) > 0 {
|
||||
for _, diffHost := range opts.DifferentHost {
|
||||
if !uuidRegex.MatchString(diffHost) {
|
||||
return nil, fmt.Errorf("The hosts in DifferentHost must be in UUID format.")
|
||||
}
|
||||
}
|
||||
sh["different_host"] = opts.DifferentHost
|
||||
}
|
||||
|
||||
if len(opts.SameHost) > 0 {
|
||||
for _, sameHost := range opts.SameHost {
|
||||
if !uuidRegex.MatchString(sameHost) {
|
||||
return nil, fmt.Errorf("The hosts in SameHost must be in UUID format.")
|
||||
}
|
||||
}
|
||||
sh["same_host"] = opts.SameHost
|
||||
}
|
||||
|
||||
/* Query can be something simple like:
|
||||
[">=", "$free_ram_mb", 1024]
|
||||
|
||||
Or more complex like:
|
||||
['and',
|
||||
['>=', '$free_ram_mb', 1024],
|
||||
['>=', '$free_disk_mb', 200 * 1024]
|
||||
]
|
||||
|
||||
Because of the possible complexity, just make sure the length is a minimum of 3.
|
||||
*/
|
||||
if len(opts.Query) > 0 {
|
||||
if len(opts.Query) < 3 {
|
||||
return nil, fmt.Errorf("Query must be a conditional statement in the format of [op,variable,value]")
|
||||
}
|
||||
sh["query"] = opts.Query
|
||||
}
|
||||
|
||||
if opts.TargetCell != "" {
|
||||
sh["target_cell"] = opts.TargetCell
|
||||
}
|
||||
|
||||
if opts.BuildNearHostIP != "" {
|
||||
if _, _, err := net.ParseCIDR(opts.BuildNearHostIP); err != nil {
|
||||
return nil, fmt.Errorf("BuildNearHostIP must be a valid subnet in the form 192.168.1.1/24")
|
||||
}
|
||||
ipParts := strings.Split(opts.BuildNearHostIP, "/")
|
||||
sh["build_near_host_ip"] = ipParts[0]
|
||||
sh["cidr"] = "/" + ipParts[1]
|
||||
}
|
||||
|
||||
return sh, nil
|
||||
}
|
||||
|
||||
// CreateOptsExt adds a SchedulerHints option to the base CreateOpts.
|
||||
type CreateOptsExt struct {
|
||||
servers.CreateOptsBuilder
|
||||
|
||||
// SchedulerHints provides a set of hints to the scheduler.
|
||||
SchedulerHints SchedulerHintsBuilder
|
||||
}
|
||||
|
||||
// ToServerCreateMap adds the SchedulerHints option to the base server creation options.
|
||||
func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
|
||||
base, err := opts.CreateOptsBuilder.ToServerCreateMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schedulerHints, err := opts.SchedulerHints.ToServerSchedulerHintsMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(schedulerHints) == 0 {
|
||||
return base, nil
|
||||
}
|
||||
|
||||
base["os:scheduler_hints"] = schedulerHints
|
||||
|
||||
return base, nil
|
||||
}
|
||||
130
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests_test.go
generated
vendored
Normal file
130
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests_test.go
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
package schedulerhints
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
)
|
||||
|
||||
func TestCreateOpts(t *testing.T) {
|
||||
base := servers.CreateOpts{
|
||||
Name: "createdserver",
|
||||
ImageRef: "asdfasdfasdf",
|
||||
FlavorRef: "performance1-1",
|
||||
}
|
||||
|
||||
schedulerHints := SchedulerHints{
|
||||
Group: "101aed42-22d9-4a3e-9ba1-21103b0d1aba",
|
||||
DifferentHost: []string{
|
||||
"a0cf03a5-d921-4877-bb5c-86d26cf818e1",
|
||||
"8c19174f-4220-44f0-824a-cd1eeef10287",
|
||||
},
|
||||
SameHost: []string{
|
||||
"a0cf03a5-d921-4877-bb5c-86d26cf818e1",
|
||||
"8c19174f-4220-44f0-824a-cd1eeef10287",
|
||||
},
|
||||
Query: []interface{}{">=", "$free_ram_mb", "1024"},
|
||||
TargetCell: "foobar",
|
||||
BuildNearHostIP: "192.168.1.1/24",
|
||||
}
|
||||
|
||||
ext := CreateOptsExt{
|
||||
CreateOptsBuilder: base,
|
||||
SchedulerHints: schedulerHints,
|
||||
}
|
||||
|
||||
expected := `
|
||||
{
|
||||
"server": {
|
||||
"name": "createdserver",
|
||||
"imageRef": "asdfasdfasdf",
|
||||
"flavorRef": "performance1-1",
|
||||
"flavorName": "",
|
||||
"imageName": ""
|
||||
},
|
||||
"os:scheduler_hints": {
|
||||
"group": "101aed42-22d9-4a3e-9ba1-21103b0d1aba",
|
||||
"different_host": [
|
||||
"a0cf03a5-d921-4877-bb5c-86d26cf818e1",
|
||||
"8c19174f-4220-44f0-824a-cd1eeef10287"
|
||||
],
|
||||
"same_host": [
|
||||
"a0cf03a5-d921-4877-bb5c-86d26cf818e1",
|
||||
"8c19174f-4220-44f0-824a-cd1eeef10287"
|
||||
],
|
||||
"query": [
|
||||
">=", "$free_ram_mb", "1024"
|
||||
],
|
||||
"target_cell": "foobar",
|
||||
"build_near_host_ip": "192.168.1.1",
|
||||
"cidr": "/24"
|
||||
}
|
||||
}
|
||||
`
|
||||
actual, err := ext.ToServerCreateMap()
|
||||
th.AssertNoErr(t, err)
|
||||
th.CheckJSONEquals(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestCreateOptsWithComplexQuery(t *testing.T) {
|
||||
base := servers.CreateOpts{
|
||||
Name: "createdserver",
|
||||
ImageRef: "asdfasdfasdf",
|
||||
FlavorRef: "performance1-1",
|
||||
}
|
||||
|
||||
schedulerHints := SchedulerHints{
|
||||
Group: "101aed42-22d9-4a3e-9ba1-21103b0d1aba",
|
||||
DifferentHost: []string{
|
||||
"a0cf03a5-d921-4877-bb5c-86d26cf818e1",
|
||||
"8c19174f-4220-44f0-824a-cd1eeef10287",
|
||||
},
|
||||
SameHost: []string{
|
||||
"a0cf03a5-d921-4877-bb5c-86d26cf818e1",
|
||||
"8c19174f-4220-44f0-824a-cd1eeef10287",
|
||||
},
|
||||
Query: []interface{}{"and", []string{">=", "$free_ram_mb", "1024"}, []string{">=", "$free_disk_mb", "204800"}},
|
||||
TargetCell: "foobar",
|
||||
BuildNearHostIP: "192.168.1.1/24",
|
||||
}
|
||||
|
||||
ext := CreateOptsExt{
|
||||
CreateOptsBuilder: base,
|
||||
SchedulerHints: schedulerHints,
|
||||
}
|
||||
|
||||
expected := `
|
||||
{
|
||||
"server": {
|
||||
"name": "createdserver",
|
||||
"imageRef": "asdfasdfasdf",
|
||||
"flavorRef": "performance1-1",
|
||||
"flavorName": "",
|
||||
"imageName": ""
|
||||
},
|
||||
"os:scheduler_hints": {
|
||||
"group": "101aed42-22d9-4a3e-9ba1-21103b0d1aba",
|
||||
"different_host": [
|
||||
"a0cf03a5-d921-4877-bb5c-86d26cf818e1",
|
||||
"8c19174f-4220-44f0-824a-cd1eeef10287"
|
||||
],
|
||||
"same_host": [
|
||||
"a0cf03a5-d921-4877-bb5c-86d26cf818e1",
|
||||
"8c19174f-4220-44f0-824a-cd1eeef10287"
|
||||
],
|
||||
"query": [
|
||||
"and",
|
||||
[">=", "$free_ram_mb", "1024"],
|
||||
[">=", "$free_disk_mb", "204800"]
|
||||
],
|
||||
"target_cell": "foobar",
|
||||
"build_near_host_ip": "192.168.1.1",
|
||||
"cidr": "/24"
|
||||
}
|
||||
}
|
||||
`
|
||||
actual, err := ext.ToServerCreateMap()
|
||||
th.AssertNoErr(t, err)
|
||||
th.CheckJSONEquals(t, expected, actual)
|
||||
}
|
||||
@@ -242,6 +242,7 @@ func mockAddServerToGroupResponse(t *testing.T, serverID string) {
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
fmt.Fprintf(w, `{}`)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -261,5 +262,6 @@ func mockRemoveServerFromGroupResponse(t *testing.T, serverID string) {
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
fmt.Fprintf(w, `{}`)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,15 +3,44 @@ package volumeattach
|
||||
import (
|
||||
"testing"
|
||||
|
||||
fixtures "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/testing"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
"github.com/rackspace/gophercloud/testhelper/client"
|
||||
)
|
||||
|
||||
// FirstVolumeAttachment is the first result in ListOutput.
|
||||
var FirstVolumeAttachment = VolumeAttachment{
|
||||
Device: "/dev/vdd",
|
||||
ID: "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
||||
VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
}
|
||||
|
||||
// SecondVolumeAttachment is the first result in ListOutput.
|
||||
var SecondVolumeAttachment = VolumeAttachment{
|
||||
Device: "/dev/vdc",
|
||||
ID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
||||
VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
}
|
||||
|
||||
// ExpectedVolumeAttachmentSlide is the slice of results that should be parsed
|
||||
// from ListOutput, in the expected order.
|
||||
var ExpectedVolumeAttachmentSlice = []VolumeAttachment{FirstVolumeAttachment, SecondVolumeAttachment}
|
||||
|
||||
//CreatedVolumeAttachment is the parsed result from CreatedOutput.
|
||||
var CreatedVolumeAttachment = VolumeAttachment{
|
||||
Device: "/dev/vdc",
|
||||
ID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
||||
VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
HandleListSuccessfully(t)
|
||||
fixtures.HandleListSuccessfully(t)
|
||||
serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0"
|
||||
|
||||
count := 0
|
||||
@@ -30,7 +59,7 @@ func TestList(t *testing.T) {
|
||||
func TestCreate(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
HandleCreateSuccessfully(t)
|
||||
fixtures.HandleCreateSuccessfully(t)
|
||||
serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0"
|
||||
|
||||
actual, err := Create(client.ServiceClient(), serverId, CreateOpts{
|
||||
@@ -44,7 +73,7 @@ func TestCreate(t *testing.T) {
|
||||
func TestGet(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
HandleGetSuccessfully(t)
|
||||
fixtures.HandleGetSuccessfully(t)
|
||||
aId := "a26887c6-c47b-4654-abb5-dfadf7d3f804"
|
||||
serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0"
|
||||
|
||||
@@ -56,7 +85,7 @@ func TestGet(t *testing.T) {
|
||||
func TestDelete(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
HandleDeleteSuccessfully(t)
|
||||
fixtures.HandleDeleteSuccessfully(t)
|
||||
aId := "a26887c6-c47b-4654-abb5-dfadf7d3f804"
|
||||
serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0"
|
||||
|
||||
|
||||
7
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/testing/doc.go
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/testing/doc.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
This is package created is to hold fixtures (which imports testing),
|
||||
so that importing volumeattach package does not inadvertently import testing into production code
|
||||
More information here:
|
||||
https://github.com/rackspace/gophercloud/issues/473
|
||||
*/
|
||||
package testing
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build fixtures
|
||||
|
||||
package volumeattach
|
||||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -55,34 +55,6 @@ const CreateOutput = `
|
||||
}
|
||||
`
|
||||
|
||||
// FirstVolumeAttachment is the first result in ListOutput.
|
||||
var FirstVolumeAttachment = VolumeAttachment{
|
||||
Device: "/dev/vdd",
|
||||
ID: "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
||||
VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
}
|
||||
|
||||
// SecondVolumeAttachment is the first result in ListOutput.
|
||||
var SecondVolumeAttachment = VolumeAttachment{
|
||||
Device: "/dev/vdc",
|
||||
ID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
||||
VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
}
|
||||
|
||||
// ExpectedVolumeAttachmentSlide is the slice of results that should be parsed
|
||||
// from ListOutput, in the expected order.
|
||||
var ExpectedVolumeAttachmentSlice = []VolumeAttachment{FirstVolumeAttachment, SecondVolumeAttachment}
|
||||
|
||||
// CreatedVolumeAttachment is the parsed result from CreatedOutput.
|
||||
var CreatedVolumeAttachment = VolumeAttachment{
|
||||
Device: "/dev/vdc",
|
||||
ID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
||||
VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
}
|
||||
|
||||
// HandleListSuccessfully configures the test server to respond to a List request.
|
||||
func HandleListSuccessfully(t *testing.T) {
|
||||
th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments", func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -1,6 +1,8 @@
|
||||
package flavors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
@@ -66,3 +68,36 @@ func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
||||
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
|
||||
return res
|
||||
}
|
||||
|
||||
// IDFromName is a convienience function that returns a flavor's ID given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
flavorCount := 0
|
||||
flavorID := ""
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("A flavor name must be provided.")
|
||||
}
|
||||
pager := ListDetail(client, nil)
|
||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
flavorList, err := ExtractFlavors(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, f := range flavorList {
|
||||
if f.Name == name {
|
||||
flavorCount++
|
||||
flavorID = f.ID
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
switch flavorCount {
|
||||
case 0:
|
||||
return "", fmt.Errorf("Unable to find flavor: %s", name)
|
||||
case 1:
|
||||
return flavorID, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Found %d flavors matching %s", flavorCount, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
@@ -70,3 +72,38 @@ func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
|
||||
_, result.Err = client.Delete(deleteURL(client, id), nil)
|
||||
return result
|
||||
}
|
||||
|
||||
// IDFromName is a convienience function that returns an image's ID given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
imageCount := 0
|
||||
imageID := ""
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("An image name must be provided.")
|
||||
}
|
||||
pager := ListDetail(client, &ListOpts{
|
||||
Name: name,
|
||||
})
|
||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
imageList, err := ExtractImages(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, i := range imageList {
|
||||
if i.Name == name {
|
||||
imageCount++
|
||||
imageID = i.ID
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
switch imageCount {
|
||||
case 0:
|
||||
return "", fmt.Errorf("Unable to find image: %s", name)
|
||||
case 1:
|
||||
return imageID, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Found %d images matching %s", imageCount, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ package servers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/images"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
@@ -14,6 +17,7 @@ import (
|
||||
type ListOptsBuilder interface {
|
||||
ToServerListQuery() (string, error)
|
||||
}
|
||||
|
||||
// ListOpts allows the filtering and sorting of paginated collections through
|
||||
// the API. Filtering is achieved by passing in struct field values that map to
|
||||
// the server attributes you want to see returned. Marker and Limit are used
|
||||
@@ -45,6 +49,9 @@ type ListOpts struct {
|
||||
|
||||
// Integer value for the limit of values to return.
|
||||
Limit int `q:"limit"`
|
||||
|
||||
// Bool to show all tenants
|
||||
AllTenants bool `q:"all_tenants"`
|
||||
}
|
||||
|
||||
// ToServerListQuery formats a ListOpts into a query string.
|
||||
@@ -95,18 +102,54 @@ type Network struct {
|
||||
FixedIP string
|
||||
}
|
||||
|
||||
// Personality is an array of files that are injected into the server at launch.
|
||||
type Personality []*File
|
||||
|
||||
// File is used within CreateOpts and RebuildOpts to inject a file into the server at launch.
|
||||
// File implements the json.Marshaler interface, so when a Create or Rebuild operation is requested,
|
||||
// json.Marshal will call File's MarshalJSON method.
|
||||
type File struct {
|
||||
// Path of the file
|
||||
Path string
|
||||
// Contents of the file. Maximum content size is 255 bytes.
|
||||
Contents []byte
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the escaped file, base64 encoding the contents.
|
||||
func (f *File) MarshalJSON() ([]byte, error) {
|
||||
file := struct {
|
||||
Path string `json:"path"`
|
||||
Contents string `json:"contents"`
|
||||
}{
|
||||
Path: f.Path,
|
||||
Contents: base64.StdEncoding.EncodeToString(f.Contents),
|
||||
}
|
||||
return json.Marshal(file)
|
||||
}
|
||||
|
||||
// CreateOpts specifies server creation parameters.
|
||||
type CreateOpts struct {
|
||||
// Name [required] is the name to assign to the newly launched server.
|
||||
Name string
|
||||
|
||||
// ImageRef [required] is the ID or full URL to the image that contains the server's OS and initial state.
|
||||
// Optional if using the boot-from-volume extension.
|
||||
// ImageRef [optional; required if ImageName is not provided] is the ID or full
|
||||
// URL to the image that contains the server's OS and initial state.
|
||||
// Also optional if using the boot-from-volume extension.
|
||||
ImageRef string
|
||||
|
||||
// FlavorRef [required] is the ID or full URL to the flavor that describes the server's specs.
|
||||
// ImageName [optional; required if ImageRef is not provided] is the name of the
|
||||
// image that contains the server's OS and initial state.
|
||||
// Also optional if using the boot-from-volume extension.
|
||||
ImageName string
|
||||
|
||||
// FlavorRef [optional; required if FlavorName is not provided] is the ID or
|
||||
// full URL to the flavor that describes the server's specs.
|
||||
FlavorRef string
|
||||
|
||||
// FlavorName [optional; required if FlavorRef is not provided] is the name of
|
||||
// the flavor that describes the server's specs.
|
||||
FlavorName string
|
||||
|
||||
// SecurityGroups [optional] lists the names of the security groups to which this server should belong.
|
||||
SecurityGroups []string
|
||||
|
||||
@@ -124,9 +167,9 @@ type CreateOpts struct {
|
||||
// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
|
||||
Metadata map[string]string
|
||||
|
||||
// Personality [optional] includes the path and contents of a file to inject into the server at launch.
|
||||
// The maximum size of the file is 255 bytes (decoded).
|
||||
Personality []byte
|
||||
// Personality [optional] includes files to inject into the server at launch.
|
||||
// Create will base64-encode file contents for you.
|
||||
Personality Personality
|
||||
|
||||
// ConfigDrive [optional] enables metadata injection through a configuration drive.
|
||||
ConfigDrive bool
|
||||
@@ -148,16 +191,14 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
|
||||
|
||||
server["name"] = opts.Name
|
||||
server["imageRef"] = opts.ImageRef
|
||||
server["imageName"] = opts.ImageName
|
||||
server["flavorRef"] = opts.FlavorRef
|
||||
server["flavorName"] = opts.FlavorName
|
||||
|
||||
if opts.UserData != nil {
|
||||
encoded := base64.StdEncoding.EncodeToString(opts.UserData)
|
||||
server["user_data"] = &encoded
|
||||
}
|
||||
if opts.Personality != nil {
|
||||
encoded := base64.StdEncoding.EncodeToString(opts.Personality)
|
||||
server["personality"] = &encoded
|
||||
}
|
||||
if opts.ConfigDrive {
|
||||
server["config_drive"] = "true"
|
||||
}
|
||||
@@ -202,6 +243,10 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
|
||||
server["networks"] = networks
|
||||
}
|
||||
|
||||
if len(opts.Personality) > 0 {
|
||||
server["personality"] = opts.Personality
|
||||
}
|
||||
|
||||
return map[string]interface{}{"server": server}, nil
|
||||
}
|
||||
|
||||
@@ -215,6 +260,38 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateRes
|
||||
return res
|
||||
}
|
||||
|
||||
// If ImageRef isn't provided, use ImageName to ascertain the image ID.
|
||||
if reqBody["server"].(map[string]interface{})["imageRef"].(string) == "" {
|
||||
imageName := reqBody["server"].(map[string]interface{})["imageName"].(string)
|
||||
if imageName == "" {
|
||||
res.Err = errors.New("One and only one of ImageRef and ImageName must be provided.")
|
||||
return res
|
||||
}
|
||||
imageID, err := images.IDFromName(client, imageName)
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
reqBody["server"].(map[string]interface{})["imageRef"] = imageID
|
||||
}
|
||||
delete(reqBody["server"].(map[string]interface{}), "imageName")
|
||||
|
||||
// If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID.
|
||||
if reqBody["server"].(map[string]interface{})["flavorRef"].(string) == "" {
|
||||
flavorName := reqBody["server"].(map[string]interface{})["flavorName"].(string)
|
||||
if flavorName == "" {
|
||||
res.Err = errors.New("One and only one of FlavorRef and FlavorName must be provided.")
|
||||
return res
|
||||
}
|
||||
flavorID, err := flavors.IDFromName(client, flavorName)
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
reqBody["server"].(map[string]interface{})["flavorRef"] = flavorID
|
||||
}
|
||||
delete(reqBody["server"].(map[string]interface{}), "flavorName")
|
||||
|
||||
_, res.Err = client.Post(listURL(client), reqBody, &res.Body, nil)
|
||||
return res
|
||||
}
|
||||
@@ -391,9 +468,9 @@ type RebuildOpts struct {
|
||||
// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
|
||||
Metadata map[string]string
|
||||
|
||||
// Personality [optional] includes the path and contents of a file to inject into the server at launch.
|
||||
// The maximum size of the file is 255 bytes (decoded).
|
||||
Personality []byte
|
||||
// Personality [optional] includes files to inject into the server at launch.
|
||||
// Rebuild will base64-encode file contents for you.
|
||||
Personality Personality
|
||||
}
|
||||
|
||||
// ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON
|
||||
@@ -429,9 +506,8 @@ func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
|
||||
server["metadata"] = opts.Metadata
|
||||
}
|
||||
|
||||
if opts.Personality != nil {
|
||||
encoded := base64.StdEncoding.EncodeToString(opts.Personality)
|
||||
server["personality"] = &encoded
|
||||
if len(opts.Personality) > 0 {
|
||||
server["personality"] = opts.Personality
|
||||
}
|
||||
|
||||
return map[string]interface{}{"rebuild": server}, nil
|
||||
@@ -678,9 +754,7 @@ func Metadatum(client *gophercloud.ServiceClient, id, key string) GetMetadatumRe
|
||||
// DeleteMetadatum will delete the key-value pair with the given key for the given server ID.
|
||||
func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) DeleteMetadatumResult {
|
||||
var res DeleteMetadatumResult
|
||||
_, res.Err = client.Delete(metadatumURL(client, id, key), &gophercloud.RequestOpts{
|
||||
JSONResponse: &res.Body,
|
||||
})
|
||||
_, res.Err = client.Delete(metadatumURL(client, id, key), nil)
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -743,3 +817,36 @@ func CreateImage(client *gophercloud.ServiceClient, serverId string, opts Create
|
||||
res.Header = response.Header
|
||||
return res
|
||||
}
|
||||
|
||||
// IDFromName is a convienience function that returns a server's ID given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
serverCount := 0
|
||||
serverID := ""
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("A server name must be provided.")
|
||||
}
|
||||
pager := List(client, nil)
|
||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
serverList, err := ExtractServers(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, s := range serverList {
|
||||
if s.Name == name {
|
||||
serverCount++
|
||||
serverID = s.ID
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
switch serverCount {
|
||||
case 0:
|
||||
return "", fmt.Errorf("Unable to find server: %s", name)
|
||||
case 1:
|
||||
return serverID, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Found %d servers matching %s", serverCount, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package servers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
@@ -334,3 +336,38 @@ func TestCreateServerImage(t *testing.T) {
|
||||
_, err := CreateImage(client.ServiceClient(), "serverimage", CreateImageOpts{Name: "test"}).ExtractImageID()
|
||||
th.AssertNoErr(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalPersonality(t *testing.T) {
|
||||
name := "/etc/test"
|
||||
contents := []byte("asdfasdf")
|
||||
|
||||
personality := Personality{
|
||||
&File{
|
||||
Path: name,
|
||||
Contents: contents,
|
||||
},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(personality)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var actual []map[string]string
|
||||
err = json.Unmarshal(data, &actual)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(actual) != 1 {
|
||||
t.Fatal("expected personality length 1")
|
||||
}
|
||||
|
||||
if actual[0]["path"] != name {
|
||||
t.Fatal("file path incorrect")
|
||||
}
|
||||
|
||||
if actual[0]["contents"] != base64.StdEncoding.EncodeToString(contents) {
|
||||
t.Fatal("file contents incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ var (
|
||||
// ErrDomainNameProvided is returned if you attempt to authenticate with a DomainName.
|
||||
ErrDomainNameProvided = unacceptedAttributeErr("DomainName")
|
||||
|
||||
// ErrUsernameRequired is returned if you attempt ot authenticate without a Username.
|
||||
// ErrUsernameRequired is returned if you attempt to authenticate without a Username.
|
||||
ErrUsernameRequired = errors.New("You must supply a Username in your AuthOptions.")
|
||||
|
||||
// ErrPasswordRequired is returned if you don't provide a password.
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package tokens
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
|
||||
type AuthOptionsBuilder interface {
|
||||
@@ -38,21 +42,25 @@ func (auth AuthOptions) ToTokenCreateMap() (map[string]interface{}, error) {
|
||||
return nil, ErrDomainNameProvided
|
||||
}
|
||||
|
||||
// Username and Password are always required.
|
||||
if auth.Username == "" {
|
||||
return nil, ErrUsernameRequired
|
||||
}
|
||||
if auth.Password == "" {
|
||||
return nil, ErrPasswordRequired
|
||||
}
|
||||
|
||||
// Populate the request map.
|
||||
authMap := make(map[string]interface{})
|
||||
|
||||
if auth.Username != "" {
|
||||
if auth.Password != "" {
|
||||
authMap["passwordCredentials"] = map[string]interface{}{
|
||||
"username": auth.Username,
|
||||
"password": auth.Password,
|
||||
}
|
||||
} else {
|
||||
return nil, ErrPasswordRequired
|
||||
}
|
||||
} else if auth.TokenID != "" {
|
||||
authMap["token"] = map[string]interface{}{
|
||||
"id": auth.TokenID,
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("You must provide either username/password or tenantID/token values.")
|
||||
}
|
||||
|
||||
if auth.TenantID != "" {
|
||||
authMap["tenantId"] = auth.TenantID
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package tokens
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
@@ -22,7 +23,7 @@ func tokenPostErr(t *testing.T, options gophercloud.AuthOptions, expectedErr err
|
||||
HandleTokenPost(t, "")
|
||||
|
||||
actualErr := Create(client.ServiceClient(), AuthOptions{options}).Err
|
||||
th.CheckEquals(t, expectedErr, actualErr)
|
||||
th.CheckDeepEquals(t, expectedErr, actualErr)
|
||||
}
|
||||
|
||||
func TestCreateWithPassword(t *testing.T) {
|
||||
@@ -128,7 +129,7 @@ func TestRequireUsername(t *testing.T) {
|
||||
Password: "thing",
|
||||
}
|
||||
|
||||
tokenPostErr(t, options, ErrUsernameRequired)
|
||||
tokenPostErr(t, options, fmt.Errorf("You must provide either username/password or tenantID/token values."))
|
||||
}
|
||||
|
||||
func TestRequirePassword(t *testing.T) {
|
||||
|
||||
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/roles/doc.go
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/roles/doc.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package roles provides information and interaction with the roles API
|
||||
// resource for the OpenStack Identity service.
|
||||
package roles
|
||||
50
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/roles/requests.go
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/roles/requests.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
package roles
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ListAssignmentsOptsBuilder allows extensions to add additional parameters to
|
||||
// the ListAssignments request.
|
||||
type ListAssignmentsOptsBuilder interface {
|
||||
ToRolesListAssignmentsQuery() (string, error)
|
||||
}
|
||||
|
||||
// ListAssignmentsOpts allows you to query the ListAssignments method.
|
||||
// Specify one of or a combination of GroupId, RoleId, ScopeDomainId, ScopeProjectId,
|
||||
// and/or UserId to search for roles assigned to corresponding entities.
|
||||
// Effective lists effective assignments at the user, project, and domain level,
|
||||
// allowing for the effects of group membership.
|
||||
type ListAssignmentsOpts struct {
|
||||
GroupId string `q:"group.id"`
|
||||
RoleId string `q:"role.id"`
|
||||
ScopeDomainId string `q:"scope.domain.id"`
|
||||
ScopeProjectId string `q:"scope.project.id"`
|
||||
UserId string `q:"user.id"`
|
||||
Effective bool `q:"effective"`
|
||||
}
|
||||
|
||||
// ToRolesListAssignmentsQuery formats a ListAssignmentsOpts into a query string.
|
||||
func (opts ListAssignmentsOpts) ToRolesListAssignmentsQuery() (string, error) {
|
||||
q, err := gophercloud.BuildQueryString(opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return q.String(), nil
|
||||
}
|
||||
|
||||
// ListAssignments enumerates the roles assigned to a specified resource.
|
||||
func ListAssignments(client *gophercloud.ServiceClient, opts ListAssignmentsOptsBuilder) pagination.Pager {
|
||||
url := listAssignmentsURL(client)
|
||||
query, err := opts.ToRolesListAssignmentsQuery()
|
||||
if err != nil {
|
||||
return pagination.Pager{Err: err}
|
||||
}
|
||||
url += query
|
||||
createPage := func(r pagination.PageResult) pagination.Page {
|
||||
return RoleAssignmentsPage{pagination.LinkedPageBase{PageResult: r}}
|
||||
}
|
||||
|
||||
return pagination.NewPager(client, url, createPage)
|
||||
}
|
||||
104
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/roles/requests_test.go
generated
vendored
Normal file
104
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/roles/requests_test.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
package roles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
"github.com/rackspace/gophercloud/testhelper"
|
||||
"github.com/rackspace/gophercloud/testhelper/client"
|
||||
)
|
||||
|
||||
func TestListSinglePage(t *testing.T) {
|
||||
testhelper.SetupHTTP()
|
||||
defer testhelper.TeardownHTTP()
|
||||
|
||||
testhelper.Mux.HandleFunc("/role_assignments", func(w http.ResponseWriter, r *http.Request) {
|
||||
testhelper.TestMethod(t, r, "GET")
|
||||
testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `
|
||||
{
|
||||
"role_assignments": [
|
||||
{
|
||||
"links": {
|
||||
"assignment": "http://identity:35357/v3/domains/161718/users/313233/roles/123456"
|
||||
},
|
||||
"role": {
|
||||
"id": "123456"
|
||||
},
|
||||
"scope": {
|
||||
"domain": {
|
||||
"id": "161718"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"id": "313233"
|
||||
}
|
||||
},
|
||||
{
|
||||
"links": {
|
||||
"assignment": "http://identity:35357/v3/projects/456789/groups/101112/roles/123456",
|
||||
"membership": "http://identity:35357/v3/groups/101112/users/313233"
|
||||
},
|
||||
"role": {
|
||||
"id": "123456"
|
||||
},
|
||||
"scope": {
|
||||
"project": {
|
||||
"id": "456789"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"id": "313233"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "http://identity:35357/v3/role_assignments?effective",
|
||||
"previous": null,
|
||||
"next": null
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
count := 0
|
||||
err := ListAssignments(client.ServiceClient(), ListAssignmentsOpts{}).EachPage(func(page pagination.Page) (bool, error) {
|
||||
count++
|
||||
actual, err := ExtractRoleAssignments(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
expected := []RoleAssignment{
|
||||
RoleAssignment{
|
||||
Role: Role{ID: "123456"},
|
||||
Scope: Scope{Domain: Domain{ID: "161718"}},
|
||||
User: User{ID: "313233"},
|
||||
Group: Group{},
|
||||
},
|
||||
RoleAssignment{
|
||||
Role: Role{ID: "123456"},
|
||||
Scope: Scope{Project: Project{ID: "456789"}},
|
||||
User: User{ID: "313233"},
|
||||
Group: Group{},
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("Expected %#v, got %#v", expected, actual)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error while paging: %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Errorf("Expected 1 page, got %d", count)
|
||||
}
|
||||
}
|
||||
81
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/roles/results.go
generated
vendored
Normal file
81
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/roles/results.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package roles
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// RoleAssignment is the result of a role assignments query.
|
||||
type RoleAssignment struct {
|
||||
Role Role `json:"role,omitempty"`
|
||||
Scope Scope `json:"scope,omitempty"`
|
||||
User User `json:"user,omitempty"`
|
||||
Group Group `json:"group,omitempty"`
|
||||
}
|
||||
|
||||
type Role struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
type Scope struct {
|
||||
Domain Domain `json:"domain,omitempty"`
|
||||
Project Project `json:"domain,omitempty"`
|
||||
}
|
||||
|
||||
type Domain struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
// RoleAssignmentsPage is a single page of RoleAssignments results.
|
||||
type RoleAssignmentsPage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the page contains no results.
|
||||
func (p RoleAssignmentsPage) IsEmpty() (bool, error) {
|
||||
roleAssignments, err := ExtractRoleAssignments(p)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return len(roleAssignments) == 0, nil
|
||||
}
|
||||
|
||||
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
|
||||
func (page RoleAssignmentsPage) NextPageURL() (string, error) {
|
||||
type resp struct {
|
||||
Links struct {
|
||||
Next string `mapstructure:"next"`
|
||||
} `mapstructure:"links"`
|
||||
}
|
||||
|
||||
var r resp
|
||||
err := mapstructure.Decode(page.Body, &r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return r.Links.Next, nil
|
||||
}
|
||||
|
||||
// ExtractRoleAssignments extracts a slice of RoleAssignments from a Collection acquired from List.
|
||||
func ExtractRoleAssignments(page pagination.Page) ([]RoleAssignment, error) {
|
||||
var response struct {
|
||||
RoleAssignments []RoleAssignment `mapstructure:"role_assignments"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(page.(RoleAssignmentsPage).Body, &response)
|
||||
return response.RoleAssignments, err
|
||||
}
|
||||
7
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/roles/urls.go
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/roles/urls.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package roles
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
|
||||
func listAssignmentsURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("role_assignments")
|
||||
}
|
||||
15
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/roles/urls_test.go
generated
vendored
Normal file
15
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/identity/v3/roles/urls_test.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package roles
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
func TestListAssignmentsURL(t *testing.T) {
|
||||
client := gophercloud.ServiceClient{Endpoint: "http://localhost:5000/v3/"}
|
||||
url := listAssignmentsURL(&client)
|
||||
if url != "http://localhost:5000/v3/role_assignments" {
|
||||
t.Errorf("Unexpected list URL generated: [%s]", url)
|
||||
}
|
||||
}
|
||||
@@ -212,9 +212,9 @@ func TestUpdate(t *testing.T) {
|
||||
"name": "fw",
|
||||
"admin_state_up": false,
|
||||
"tenant_id": "b4eedccc6fb74fa8a7ad6b08382b852b",
|
||||
"firewall_policy_id": "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c"
|
||||
"firewall_policy_id": "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c",
|
||||
"id": "ea5b5315-64f6-4ea3-8e58-981cc37c6576",
|
||||
"description": "OpenStack firewall",
|
||||
"description": "OpenStack firewall"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -296,6 +296,7 @@ func TestAssociateHealthMonitor(t *testing.T) {
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprintf(w, `{}`)
|
||||
})
|
||||
|
||||
_, err := AssociateMonitor(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", "b624decf-d5d3-4c66-9a3d-f047e7786181").Extract()
|
||||
|
||||
@@ -45,6 +45,9 @@ type CreateOpts struct {
|
||||
// Required. Human-readable name for the VIP. Does not have to be unique.
|
||||
Name string
|
||||
|
||||
// Required for admins. Indicates the owner of the VIP.
|
||||
TenantID string
|
||||
|
||||
// Optional. Describes the security group.
|
||||
Description string
|
||||
}
|
||||
@@ -62,6 +65,7 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
|
||||
|
||||
type secgroup struct {
|
||||
Name string `json:"name"`
|
||||
TenantID string `json:"tenant_id,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
@@ -71,6 +75,7 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
|
||||
|
||||
reqBody := request{SecGroup: secgroup{
|
||||
Name: opts.Name,
|
||||
TenantID: opts.TenantID,
|
||||
Description: opts.Description,
|
||||
}}
|
||||
|
||||
@@ -91,3 +96,36 @@ func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
|
||||
_, res.Err = c.Delete(resourceURL(c, id), nil)
|
||||
return res
|
||||
}
|
||||
|
||||
// IDFromName is a convenience function that returns a security group's ID given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
securityGroupCount := 0
|
||||
securityGroupID := ""
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("A security group name must be provided.")
|
||||
}
|
||||
pager := List(client, ListOpts{})
|
||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
securityGroupList, err := ExtractGroups(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, s := range securityGroupList {
|
||||
if s.Name == name {
|
||||
securityGroupCount++
|
||||
securityGroupID = s.ID
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
switch securityGroupCount {
|
||||
case 0:
|
||||
return "", fmt.Errorf("Unable to find security group: %s", name)
|
||||
case 1:
|
||||
return securityGroupID, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Found %d security groups matching %s", securityGroupCount, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,9 @@ type CreateOpts struct {
|
||||
// attribute matches the specified IP prefix as the source IP address of the
|
||||
// IP packet.
|
||||
RemoteIPPrefix string
|
||||
|
||||
// Required for admins. Indicates the owner of the VIP.
|
||||
TenantID string
|
||||
}
|
||||
|
||||
// Create is an operation which provisions a new security group with default
|
||||
@@ -133,6 +136,7 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
RemoteGroupID string `json:"remote_group_id,omitempty"`
|
||||
RemoteIPPrefix string `json:"remote_ip_prefix,omitempty"`
|
||||
TenantID string `json:"tenant_id,omitempty"`
|
||||
}
|
||||
|
||||
type request struct {
|
||||
@@ -148,6 +152,7 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
|
||||
Protocol: opts.Protocol,
|
||||
RemoteGroupID: opts.RemoteGroupID,
|
||||
RemoteIPPrefix: opts.RemoteIPPrefix,
|
||||
TenantID: opts.TenantID,
|
||||
}}
|
||||
|
||||
_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package networks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
@@ -189,3 +191,36 @@ func Delete(c *gophercloud.ServiceClient, networkID string) DeleteResult {
|
||||
_, res.Err = c.Delete(deleteURL(c, networkID), nil)
|
||||
return res
|
||||
}
|
||||
|
||||
// IDFromName is a convenience function that returns a network's ID given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
networkCount := 0
|
||||
networkID := ""
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("A network name must be provided.")
|
||||
}
|
||||
pager := List(client, nil)
|
||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
networkList, err := ExtractNetworks(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, n := range networkList {
|
||||
if n.Name == name {
|
||||
networkCount++
|
||||
networkID = n.ID
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
switch networkCount {
|
||||
case 0:
|
||||
return "", fmt.Errorf("Unable to find network: %s", name)
|
||||
case 1:
|
||||
return networkID, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Found %d networks matching %s", networkCount, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +204,7 @@ func TestCreateWithOptionalFields(t *testing.T) {
|
||||
`)
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprintf(w, `{}`)
|
||||
})
|
||||
|
||||
iTrue := true
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package ports
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
@@ -223,3 +225,36 @@ func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
|
||||
_, res.Err = c.Delete(deleteURL(c, id), nil)
|
||||
return res
|
||||
}
|
||||
|
||||
// IDFromName is a convenience function that returns a port's ID given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
portCount := 0
|
||||
portID := ""
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("A port name must be provided.")
|
||||
}
|
||||
pager := List(client, nil)
|
||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
portList, err := ExtractPorts(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, p := range portList {
|
||||
if p.Name == name {
|
||||
portCount++
|
||||
portID = p.ID
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
switch portCount {
|
||||
case 0:
|
||||
return "", fmt.Errorf("Unable to find port: %s", name)
|
||||
case 1:
|
||||
return portID, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Found %d ports matching %s", portCount, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package subnets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
@@ -200,10 +202,10 @@ func (opts UpdateOpts) ToSubnetUpdateMap() (map[string]interface{}, error) {
|
||||
if opts.GatewayIP != "" {
|
||||
s["gateway_ip"] = opts.GatewayIP
|
||||
}
|
||||
if len(opts.DNSNameservers) != 0 {
|
||||
if opts.DNSNameservers != nil {
|
||||
s["dns_nameservers"] = opts.DNSNameservers
|
||||
}
|
||||
if len(opts.HostRoutes) != 0 {
|
||||
if opts.HostRoutes != nil {
|
||||
s["host_routes"] = opts.HostRoutes
|
||||
}
|
||||
|
||||
@@ -234,3 +236,36 @@ func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
|
||||
_, res.Err = c.Delete(deleteURL(c, id), nil)
|
||||
return res
|
||||
}
|
||||
|
||||
// IDFromName is a convenience function that returns a subnet's ID given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
subnetCount := 0
|
||||
subnetID := ""
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("A subnet name must be provided.")
|
||||
}
|
||||
pager := List(client, nil)
|
||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
subnetList, err := ExtractSubnets(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, s := range subnetList {
|
||||
if s.Name == name {
|
||||
subnetCount++
|
||||
subnetID = s.ID
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
switch subnetCount {
|
||||
case 0:
|
||||
return "", fmt.Errorf("Unable to find subnet: %s", name)
|
||||
case 1:
|
||||
return subnetID, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Found %d subnets matching %s", subnetCount, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ type AllocationPool struct {
|
||||
// HostRoute represents a route that should be used by devices with IPs from
|
||||
// a subnet (not including local subnet route).
|
||||
type HostRoute struct {
|
||||
DestinationCIDR string `json:"destination"`
|
||||
NextHop string `json:"nexthop"`
|
||||
DestinationCIDR string `mapstructure:"destination" json:"destination"`
|
||||
NextHop string `mapstructure:"nexthop" json:"nexthop"`
|
||||
}
|
||||
|
||||
// Subnet represents a subnet. See package documentation for a top-level
|
||||
|
||||
54
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/subnets/results_test.go
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/networking/v2/subnets/results_test.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package subnets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/rackspace/gophercloud"
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHostRoute(t *testing.T) {
|
||||
sejson := []byte(`
|
||||
{"subnet": {
|
||||
"name": "test-subnet",
|
||||
"enable_dhcp": false,
|
||||
"network_id": "3e66c41e-cbbd-4019-9aab-740b7e4150a0",
|
||||
"tenant_id": "f86e123198cf42d19c8854c5f80c2f06",
|
||||
"dns_nameservers": [],
|
||||
"gateway_ip": "172.16.0.1",
|
||||
"ipv6_ra_mode": null,
|
||||
"allocation_pools": [
|
||||
{
|
||||
"start": "172.16.0.2",
|
||||
"end": "172.16.255.254"
|
||||
}
|
||||
],
|
||||
"host_routes": [
|
||||
{
|
||||
"destination": "172.20.1.0/24",
|
||||
"nexthop": "172.16.0.2"
|
||||
}
|
||||
],
|
||||
"ip_version": 4,
|
||||
"ipv6_address_mode": null,
|
||||
"cidr": "172.16.0.0/16",
|
||||
"id": "6dcaa873-7115-41af-9ef5-915f73636e43",
|
||||
"subnetpool_id": null
|
||||
}}
|
||||
`)
|
||||
|
||||
var dejson interface{}
|
||||
err := json.Unmarshal(sejson, &dejson)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
|
||||
resp := commonResult{gophercloud.Result{Body: dejson}}
|
||||
subnet, err := resp.Extract()
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
route := subnet.HostRoutes[0]
|
||||
th.AssertEquals(t, route.NextHop, "172.16.0.2")
|
||||
th.AssertEquals(t, route.DestinationCIDR, "172.20.1.0/24")
|
||||
}
|
||||
@@ -43,7 +43,9 @@ func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) GetResult {
|
||||
MoreHeaders: h,
|
||||
OkCodes: []int{204},
|
||||
})
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
@@ -97,7 +99,9 @@ func Update(c *gophercloud.ServiceClient, opts UpdateOptsBuilder) UpdateResult {
|
||||
MoreHeaders: h,
|
||||
OkCodes: []int{201, 202, 204},
|
||||
})
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -114,7 +114,9 @@ func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOptsB
|
||||
MoreHeaders: h,
|
||||
OkCodes: []int{201, 202, 204},
|
||||
})
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
@@ -180,7 +182,9 @@ func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsB
|
||||
MoreHeaders: h,
|
||||
OkCodes: []int{201, 202, 204},
|
||||
})
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
@@ -193,7 +197,9 @@ func Get(c *gophercloud.ServiceClient, containerName string) GetResult {
|
||||
resp, err := c.Request("HEAD", getURL(c, containerName), gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 204},
|
||||
})
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
package objects
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
@@ -107,12 +109,18 @@ func HandleListObjectNamesSuccessfully(t *testing.T) {
|
||||
|
||||
// HandleCreateTextObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux
|
||||
// that responds with a `Create` response. A Content-Type of "text/plain" is expected.
|
||||
func HandleCreateTextObjectSuccessfully(t *testing.T) {
|
||||
func HandleCreateTextObjectSuccessfully(t *testing.T, content string) {
|
||||
th.Mux.HandleFunc("/testContainer/testObject", 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", "text/plain")
|
||||
th.TestHeader(t, r, "Accept", "application/json")
|
||||
|
||||
hash := md5.New()
|
||||
io.WriteString(hash, content)
|
||||
localChecksum := hash.Sum(nil)
|
||||
|
||||
w.Header().Set("ETag", fmt.Sprintf("%x", localChecksum))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
})
|
||||
}
|
||||
@@ -120,7 +128,7 @@ func HandleCreateTextObjectSuccessfully(t *testing.T) {
|
||||
// HandleCreateTypelessObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler
|
||||
// mux that responds with a `Create` response. No Content-Type header may be present in the request, so that server-
|
||||
// side content-type detection will be triggered properly.
|
||||
func HandleCreateTypelessObjectSuccessfully(t *testing.T) {
|
||||
func HandleCreateTypelessObjectSuccessfully(t *testing.T, content string) {
|
||||
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "PUT")
|
||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
||||
@@ -130,6 +138,11 @@ func HandleCreateTypelessObjectSuccessfully(t *testing.T) {
|
||||
t.Errorf("Expected Content-Type header to be omitted, but was %#v", contentType)
|
||||
}
|
||||
|
||||
hash := md5.New()
|
||||
io.WriteString(hash, content)
|
||||
localChecksum := hash.Sum(nil)
|
||||
|
||||
w.Header().Set("ETag", fmt.Sprintf("%x", localChecksum))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package objects
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -134,10 +136,11 @@ func Download(c *gophercloud.ServiceClient, containerName, objectName string, op
|
||||
MoreHeaders: h,
|
||||
OkCodes: []int{200, 304},
|
||||
})
|
||||
|
||||
res.Body = resp.Body
|
||||
res.Err = err
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
res.Body = resp.Body
|
||||
}
|
||||
res.Err = err
|
||||
|
||||
return res
|
||||
}
|
||||
@@ -187,8 +190,9 @@ func (opts CreateOpts) ToObjectCreateParams() (map[string]string, string, error)
|
||||
return h, q.String(), nil
|
||||
}
|
||||
|
||||
// Create is a function that creates a new object or replaces an existing object.
|
||||
func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.Reader, opts CreateOptsBuilder) CreateResult {
|
||||
// Create is a function that creates a new object or replaces an existing object. If the returned response's ETag
|
||||
// header fails to match the local checksum, the failed request will automatically be retried up to a maximum of 3 times.
|
||||
func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.ReadSeeker, opts CreateOptsBuilder) CreateResult {
|
||||
var res CreateResult
|
||||
|
||||
url := createURL(c, containerName, objectName)
|
||||
@@ -208,16 +212,39 @@ func Create(c *gophercloud.ServiceClient, containerName, objectName string, cont
|
||||
url += query
|
||||
}
|
||||
|
||||
hash := md5.New()
|
||||
|
||||
contentBuffer := bytes.NewBuffer([]byte{})
|
||||
_, err := io.Copy(contentBuffer, io.TeeReader(content, hash))
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
localChecksum := hash.Sum(nil)
|
||||
h["ETag"] = fmt.Sprintf("%x", localChecksum)
|
||||
|
||||
ropts := gophercloud.RequestOpts{
|
||||
RawBody: content,
|
||||
RawBody: strings.NewReader(contentBuffer.String()),
|
||||
MoreHeaders: h,
|
||||
}
|
||||
|
||||
resp, err := c.Request("PUT", url, ropts)
|
||||
res.Header = resp.Header
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
if resp.Header.Get("ETag") == fmt.Sprintf("%x", localChecksum) {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
res.Err = fmt.Errorf("Local checksum does not match API ETag header")
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// CopyOptsBuilder allows extensions to add additional parameters to the
|
||||
// Copy request.
|
||||
@@ -270,7 +297,9 @@ func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts C
|
||||
MoreHeaders: h,
|
||||
OkCodes: []int{201},
|
||||
})
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
@@ -310,7 +339,9 @@ func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts
|
||||
}
|
||||
|
||||
resp, err := c.Delete(url, nil)
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
@@ -354,7 +385,9 @@ func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts Ge
|
||||
resp, err := c.Request("HEAD", url, gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 204},
|
||||
})
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
@@ -410,7 +443,9 @@ func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts
|
||||
resp, err := c.Request("POST", url, gophercloud.RequestOpts{
|
||||
MoreHeaders: h,
|
||||
})
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ package objects
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
@@ -83,24 +86,44 @@ func TestListObjectNames(t *testing.T) {
|
||||
func TestCreateObject(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
HandleCreateTextObjectSuccessfully(t)
|
||||
|
||||
content := bytes.NewBufferString("Did gyre and gimble in the wabe")
|
||||
content := "Did gyre and gimble in the wabe"
|
||||
|
||||
HandleCreateTextObjectSuccessfully(t, content)
|
||||
|
||||
options := &CreateOpts{ContentType: "text/plain"}
|
||||
res := Create(fake.ServiceClient(), "testContainer", "testObject", content, options)
|
||||
res := Create(fake.ServiceClient(), "testContainer", "testObject", strings.NewReader(content), options)
|
||||
th.AssertNoErr(t, res.Err)
|
||||
}
|
||||
|
||||
func TestCreateObjectWithoutContentType(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
HandleCreateTypelessObjectSuccessfully(t)
|
||||
|
||||
content := bytes.NewBufferString("The sky was the color of television, tuned to a dead channel.")
|
||||
res := Create(fake.ServiceClient(), "testContainer", "testObject", content, &CreateOpts{})
|
||||
content := "The sky was the color of television, tuned to a dead channel."
|
||||
|
||||
HandleCreateTypelessObjectSuccessfully(t, content)
|
||||
|
||||
res := Create(fake.ServiceClient(), "testContainer", "testObject", strings.NewReader(content), &CreateOpts{})
|
||||
th.AssertNoErr(t, res.Err)
|
||||
}
|
||||
|
||||
func TestErrorIsRaisedForChecksumMismatch(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
|
||||
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("ETag", "acbd18db4cc2f85cedef654fccc4a4d8")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
})
|
||||
|
||||
content := strings.NewReader("The sky was the color of television, tuned to a dead channel.")
|
||||
res := Create(fake.ServiceClient(), "testContainer", "testObject", content, &CreateOpts{})
|
||||
|
||||
err := fmt.Errorf("Local checksum does not match API ETag header")
|
||||
th.AssertDeepEquals(t, err, res.Err)
|
||||
}
|
||||
|
||||
func TestCopyObject(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
|
||||
@@ -67,7 +67,7 @@ const FindOutput = `
|
||||
"events": [
|
||||
{
|
||||
"resource_name": "hello_world",
|
||||
"event_time": "2015-02-05T21:33:11Z",
|
||||
"event_time": "2015-02-05T21:33:11",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
|
||||
@@ -90,7 +90,7 @@ const FindOutput = `
|
||||
},
|
||||
{
|
||||
"resource_name": "hello_world",
|
||||
"event_time": "2015-02-05T21:33:27Z",
|
||||
"event_time": "2015-02-05T21:33:27",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
|
||||
@@ -184,7 +184,7 @@ const ListOutput = `
|
||||
"events": [
|
||||
{
|
||||
"resource_name": "hello_world",
|
||||
"event_time": "2015-02-05T21:33:11Z",
|
||||
"event_time": "2015-02-05T21:33:11",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
|
||||
@@ -207,7 +207,7 @@ const ListOutput = `
|
||||
},
|
||||
{
|
||||
"resource_name": "hello_world",
|
||||
"event_time": "2015-02-05T21:33:27Z",
|
||||
"event_time": "2015-02-05T21:33:27",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
|
||||
@@ -309,7 +309,7 @@ const ListResourceEventsOutput = `
|
||||
"events": [
|
||||
{
|
||||
"resource_name": "hello_world",
|
||||
"event_time": "2015-02-05T21:33:11Z",
|
||||
"event_time": "2015-02-05T21:33:11",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
|
||||
@@ -332,7 +332,7 @@ const ListResourceEventsOutput = `
|
||||
},
|
||||
{
|
||||
"resource_name": "hello_world",
|
||||
"event_time": "2015-02-05T21:33:27Z",
|
||||
"event_time": "2015-02-05T21:33:27",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
|
||||
@@ -408,7 +408,7 @@ const GetOutput = `
|
||||
{
|
||||
"event":{
|
||||
"resource_name": "hello_world",
|
||||
"event_time": "2015-02-05T21:33:27Z",
|
||||
"event_time": "2015-02-05T21:33:27",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
|
||||
|
||||
@@ -57,7 +57,7 @@ func (r FindResult) Extract() ([]Event, error) {
|
||||
for i, eventRaw := range events {
|
||||
event := eventRaw.(map[string]interface{})
|
||||
if date, ok := event["event_time"]; ok && date != nil {
|
||||
t, err := time.Parse(time.RFC3339, date.(string))
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func ExtractEvents(page pagination.Page) ([]Event, error) {
|
||||
for i, eventRaw := range events {
|
||||
event := eventRaw.(map[string]interface{})
|
||||
if date, ok := event["event_time"]; ok && date != nil {
|
||||
t, err := time.Parse(time.RFC3339, date.(string))
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -161,7 +161,7 @@ func (r GetResult) Extract() (*Event, error) {
|
||||
event := r.Body.(map[string]interface{})["event"].(map[string]interface{})
|
||||
|
||||
if date, ok := event["event_time"]; ok && date != nil {
|
||||
t, err := time.Parse(time.RFC3339, date.(string))
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ const FindOutput = `
|
||||
],
|
||||
"logical_resource_id": "hello_world",
|
||||
"resource_status_reason": "state changed",
|
||||
"updated_time": "2015-02-05T21:33:11Z",
|
||||
"updated_time": "2015-02-05T21:33:11",
|
||||
"required_by": [],
|
||||
"resource_status": "CREATE_IN_PROGRESS",
|
||||
"physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
|
||||
@@ -117,7 +117,7 @@ const ListOutput = `{
|
||||
],
|
||||
"logical_resource_id": "hello_world",
|
||||
"resource_status_reason": "state changed",
|
||||
"updated_time": "2015-02-05T21:33:11Z",
|
||||
"updated_time": "2015-02-05T21:33:11",
|
||||
"required_by": [],
|
||||
"resource_status": "CREATE_IN_PROGRESS",
|
||||
"physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
|
||||
@@ -188,7 +188,7 @@ const GetOutput = `
|
||||
],
|
||||
"logical_resource_id": "wordpress_instance",
|
||||
"resource_status": "CREATE_COMPLETE",
|
||||
"updated_time": "2014-12-10T18:34:35Z",
|
||||
"updated_time": "2014-12-10T18:34:35",
|
||||
"required_by": [],
|
||||
"resource_status_reason": "state changed",
|
||||
"physical_resource_id": "00e3a2fe-c65d-403c-9483-4db9930dd194",
|
||||
|
||||
@@ -25,12 +25,6 @@ type ListOptsBuilder interface {
|
||||
// ListOpts allows the filtering and sorting of paginated collections through
|
||||
// the API. Marker and Limit are used for pagination.
|
||||
type ListOpts struct {
|
||||
// The stack resource ID with which to start the listing.
|
||||
Marker string `q:"marker"`
|
||||
|
||||
// Integer value for the limit of values to return.
|
||||
Limit int `q:"limit"`
|
||||
|
||||
// Include resources from nest stacks up to Depth levels of recursion.
|
||||
Depth int `q:"nested_depth"`
|
||||
}
|
||||
@@ -57,9 +51,7 @@ func List(client *gophercloud.ServiceClient, stackName, stackID string, opts Lis
|
||||
}
|
||||
|
||||
createPageFn := func(r pagination.PageResult) pagination.Page {
|
||||
p := ResourcePage{pagination.MarkerPageBase{PageResult: r}}
|
||||
p.MarkerPageBase.Owner = p
|
||||
return p
|
||||
return ResourcePage{pagination.SinglePageBase(r)}
|
||||
}
|
||||
|
||||
return pagination.NewPager(client, url, createPageFn)
|
||||
|
||||
@@ -48,7 +48,7 @@ func (r FindResult) Extract() ([]Resource, error) {
|
||||
for i, resourceRaw := range resources {
|
||||
resource := resourceRaw.(map[string]interface{})
|
||||
if date, ok := resource["updated_time"]; ok && date != nil {
|
||||
t, err := time.Parse(time.RFC3339, date.(string))
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func (r FindResult) Extract() ([]Resource, error) {
|
||||
// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
|
||||
// data provided through the ExtractResources call.
|
||||
type ResourcePage struct {
|
||||
pagination.MarkerPageBase
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty returns true if a page contains no Server results.
|
||||
@@ -109,7 +109,7 @@ func ExtractResources(page pagination.Page) ([]Resource, error) {
|
||||
for i, resourceRaw := range resources {
|
||||
resource := resourceRaw.(map[string]interface{})
|
||||
if date, ok := resource["updated_time"]; ok && date != nil {
|
||||
t, err := time.Parse(time.RFC3339, date.(string))
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func (r GetResult) Extract() (*Resource, error) {
|
||||
resource := r.Body.(map[string]interface{})["resource"].(map[string]interface{})
|
||||
|
||||
if date, ok := resource["updated_time"]; ok && date != nil {
|
||||
t, err := time.Parse(time.RFC3339, date.(string))
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ const FullListOutput = `
|
||||
],
|
||||
"stack_status_reason": "Stack CREATE completed successfully",
|
||||
"stack_name": "postman_stack",
|
||||
"creation_time": "2015-02-03T20:07:39Z",
|
||||
"creation_time": "2015-02-03T20:07:39",
|
||||
"updated_time": null,
|
||||
"stack_status": "CREATE_COMPLETE",
|
||||
"id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87"
|
||||
@@ -110,8 +110,8 @@ const FullListOutput = `
|
||||
],
|
||||
"stack_status_reason": "Stack successfully updated",
|
||||
"stack_name": "gophercloud-test-stack-2",
|
||||
"creation_time": "2014-12-11T17:39:16Z",
|
||||
"updated_time": "2014-12-11T17:40:37Z",
|
||||
"creation_time": "2014-12-11T17:39:16",
|
||||
"updated_time": "2014-12-11T17:40:37",
|
||||
"stack_status": "UPDATE_COMPLETE",
|
||||
"id": "db6977b2-27aa-4775-9ae7-6213212d4ada"
|
||||
}
|
||||
@@ -181,7 +181,7 @@ const GetOutput = `
|
||||
"stack_status_reason": "Stack CREATE completed successfully",
|
||||
"stack_name": "postman_stack",
|
||||
"outputs": [],
|
||||
"creation_time": "2015-02-03T20:07:39Z",
|
||||
"creation_time": "2015-02-03T20:07:39",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87",
|
||||
|
||||
@@ -100,7 +100,7 @@ func ExtractStacks(page pagination.Page) ([]ListedStack, error) {
|
||||
thisStack := (rawStacks[i]).(map[string]interface{})
|
||||
|
||||
if t, ok := thisStack["creation_time"].(string); ok && t != "" {
|
||||
creationTime, err := time.Parse(time.RFC3339, t)
|
||||
creationTime, err := time.Parse(gophercloud.STACK_TIME_FMT, t)
|
||||
if err != nil {
|
||||
return res.Stacks, err
|
||||
}
|
||||
@@ -108,7 +108,7 @@ func ExtractStacks(page pagination.Page) ([]ListedStack, error) {
|
||||
}
|
||||
|
||||
if t, ok := thisStack["updated_time"].(string); ok && t != "" {
|
||||
updatedTime, err := time.Parse(time.RFC3339, t)
|
||||
updatedTime, err := time.Parse(gophercloud.STACK_TIME_FMT, t)
|
||||
if err != nil {
|
||||
return res.Stacks, err
|
||||
}
|
||||
@@ -170,7 +170,7 @@ func (r GetResult) Extract() (*RetrievedStack, error) {
|
||||
b := r.Body.(map[string]interface{})["stack"].(map[string]interface{})
|
||||
|
||||
if date, ok := b["creation_time"]; ok && date != nil {
|
||||
t, err := time.Parse(time.RFC3339, date.(string))
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -178,7 +178,7 @@ func (r GetResult) Extract() (*RetrievedStack, error) {
|
||||
}
|
||||
|
||||
if date, ok := b["updated_time"]; ok && date != nil {
|
||||
t, err := time.Parse(time.RFC3339, date.(string))
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -249,7 +249,7 @@ func (r PreviewResult) Extract() (*PreviewedStack, error) {
|
||||
b := r.Body.(map[string]interface{})["stack"].(map[string]interface{})
|
||||
|
||||
if date, ok := b["creation_time"]; ok && date != nil {
|
||||
t, err := time.Parse(time.RFC3339, date.(string))
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -257,7 +257,7 @@ func (r PreviewResult) Extract() (*PreviewedStack, error) {
|
||||
}
|
||||
|
||||
if date, ok := b["updated_time"]; ok && date != nil {
|
||||
t, err := time.Parse(time.RFC3339, date.(string))
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
10
Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/http.go
generated
vendored
10
Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/http.go
generated
vendored
@@ -36,13 +36,19 @@ func PageResultFrom(resp *http.Response) (PageResult, error) {
|
||||
parsedBody = rawBody
|
||||
}
|
||||
|
||||
return PageResultFromParsed(resp, parsedBody), err
|
||||
}
|
||||
|
||||
// PageResultFromParsed constructs a PageResult from an HTTP response that has already had its
|
||||
// body parsed as JSON (and closed).
|
||||
func PageResultFromParsed(resp *http.Response, body interface{}) PageResult {
|
||||
return PageResult{
|
||||
Result: gophercloud.Result{
|
||||
Body: parsedBody,
|
||||
Body: body,
|
||||
Header: resp.Header,
|
||||
},
|
||||
URL: *resp.Request.URL,
|
||||
}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Request performs an HTTP request and extracts the http.Response from the result.
|
||||
|
||||
2
Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/pager.go
generated
vendored
2
Godeps/_workspace/src/github.com/rackspace/gophercloud/pagination/pager.go
generated
vendored
@@ -174,8 +174,10 @@ func (p Pager) AllPages() (Page, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pagesSlice) > 0 {
|
||||
// Remove the trailing comma.
|
||||
pagesSlice = pagesSlice[:len(pagesSlice)-1]
|
||||
}
|
||||
var b []byte
|
||||
// Combine the slice of slices in to a single slice.
|
||||
for _, slice := range pagesSlice {
|
||||
|
||||
19
Godeps/_workspace/src/github.com/rackspace/gophercloud/provider_client.go
generated
vendored
19
Godeps/_workspace/src/github.com/rackspace/gophercloud/provider_client.go
generated
vendored
@@ -85,9 +85,9 @@ type RequestOpts struct {
|
||||
// content type of the request will default to "application/json" unless overridden by MoreHeaders.
|
||||
// It's an error to specify both a JSONBody and a RawBody.
|
||||
JSONBody interface{}
|
||||
// RawBody contains an io.Reader that will be consumed by the request directly. No content-type
|
||||
// RawBody contains an io.ReadSeeker that will be consumed by the request directly. No content-type
|
||||
// will be set unless one is provided explicitly by MoreHeaders.
|
||||
RawBody io.Reader
|
||||
RawBody io.ReadSeeker
|
||||
|
||||
// JSONResponse, if provided, will be populated with the contents of the response body parsed as
|
||||
// JSON.
|
||||
@@ -124,11 +124,11 @@ var applicationJSON = "application/json"
|
||||
// Request performs an HTTP request using the ProviderClient's current HTTPClient. An authentication
|
||||
// header will automatically be provided.
|
||||
func (client *ProviderClient) Request(method, url string, options RequestOpts) (*http.Response, error) {
|
||||
var body io.Reader
|
||||
var body io.ReadSeeker
|
||||
var contentType *string
|
||||
|
||||
// Derive the content body by either encoding an arbitrary object as JSON, or by taking a provided
|
||||
// io.Reader as-is. Default the content-type to application/json.
|
||||
// io.ReadSeeker as-is. Default the content-type to application/json.
|
||||
if options.JSONBody != nil {
|
||||
if options.RawBody != nil {
|
||||
panic("Please provide only one of JSONBody or RawBody to gophercloud.Request().")
|
||||
@@ -189,6 +189,9 @@ func (client *ProviderClient) Request(method, url string, options RequestOpts) (
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error trying to re-authenticate: %s", err)
|
||||
}
|
||||
if options.RawBody != nil {
|
||||
options.RawBody.Seek(0, 0)
|
||||
}
|
||||
resp, err = client.Request(method, url, options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Successfully re-authenticated, but got error executing request: %s", err)
|
||||
@@ -224,7 +227,9 @@ func (client *ProviderClient) Request(method, url string, options RequestOpts) (
|
||||
// Parse the response body as JSON, if requested to do so.
|
||||
if options.JSONResponse != nil {
|
||||
defer resp.Body.Close()
|
||||
json.NewDecoder(resp.Body).Decode(options.JSONResponse)
|
||||
if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
@@ -260,7 +265,7 @@ func (client *ProviderClient) Post(url string, JSONBody interface{}, JSONRespons
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
|
||||
if v, ok := (JSONBody).(io.Reader); ok {
|
||||
if v, ok := (JSONBody).(io.ReadSeeker); ok {
|
||||
opts.RawBody = v
|
||||
} else if JSONBody != nil {
|
||||
opts.JSONBody = JSONBody
|
||||
@@ -278,7 +283,7 @@ func (client *ProviderClient) Put(url string, JSONBody interface{}, JSONResponse
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
|
||||
if v, ok := (JSONBody).(io.Reader); ok {
|
||||
if v, ok := (JSONBody).(io.ReadSeeker); ok {
|
||||
opts.RawBody = v
|
||||
} else if JSONBody != nil {
|
||||
opts.JSONBody = JSONBody
|
||||
|
||||
@@ -3,7 +3,8 @@ package volumes
|
||||
import (
|
||||
"testing"
|
||||
|
||||
os "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
|
||||
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
|
||||
os "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/testing"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
fake "github.com/rackspace/gophercloud/testhelper/client"
|
||||
@@ -64,7 +65,7 @@ func TestCreate(t *testing.T) {
|
||||
|
||||
os.MockCreateResponse(t)
|
||||
|
||||
n, err := Create(fake.ServiceClient(), CreateOpts{os.CreateOpts{Size: 75}}).Extract()
|
||||
n, err := Create(fake.ServiceClient(), CreateOpts{volumes.CreateOpts{Size: 75}}).Extract()
|
||||
th.AssertNoErr(t, err)
|
||||
|
||||
th.AssertEquals(t, n.Size, 4)
|
||||
@@ -72,12 +73,12 @@ func TestCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSizeRange(t *testing.T) {
|
||||
_, err := Create(fake.ServiceClient(), CreateOpts{os.CreateOpts{Size: 1}}).Extract()
|
||||
_, err := Create(fake.ServiceClient(), CreateOpts{volumes.CreateOpts{Size: 1}}).Extract()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, got none")
|
||||
}
|
||||
|
||||
_, err = Create(fake.ServiceClient(), CreateOpts{os.CreateOpts{Size: 2000}}).Extract()
|
||||
_, err = Create(fake.ServiceClient(), CreateOpts{volumes.CreateOpts{Size: 2000}}).Extract()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, got none")
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ func TestCreateOpts(t *testing.T) {
|
||||
"name": "createdserver",
|
||||
"imageRef": "asdfasdfasdf",
|
||||
"flavorRef": "performance1-1",
|
||||
"flavorName": "",
|
||||
"imageName": "",
|
||||
"block_device_mapping_v2":[
|
||||
{
|
||||
"uuid":"123456",
|
||||
|
||||
@@ -36,11 +36,8 @@ func ListDetail(client *gophercloud.ServiceClient, opts os.ListOptsBuilder) pagi
|
||||
}
|
||||
|
||||
// Get returns details about a single flavor, identity by ID.
|
||||
func Get(client *gophercloud.ServiceClient, id string) os.GetResult {
|
||||
return os.Get(client, id)
|
||||
}
|
||||
|
||||
// ExtractFlavors interprets a page of List results as Flavors.
|
||||
func ExtractFlavors(page pagination.Page) ([]os.Flavor, error) {
|
||||
return os.ExtractFlavors(page)
|
||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
||||
var res GetResult
|
||||
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
package flavors
|
||||
|
||||
import (
|
||||
os "github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
|
||||
)
|
||||
|
||||
// ListOutput is a sample response of a flavor List request.
|
||||
const ListOutput = `
|
||||
{
|
||||
@@ -103,7 +99,7 @@ const GetOutput = `
|
||||
|
||||
// Performance1Flavor is the expected result of parsing GetOutput, or the first element of
|
||||
// ListOutput.
|
||||
var Performance1Flavor = os.Flavor{
|
||||
var Performance1Flavor = Flavor{
|
||||
ID: "performance1-1",
|
||||
Disk: 20,
|
||||
RAM: 1024,
|
||||
@@ -111,10 +107,16 @@ var Performance1Flavor = os.Flavor{
|
||||
RxTxFactor: 200.0,
|
||||
Swap: 0,
|
||||
VCPUs: 1,
|
||||
ExtraSpecs: ExtraSpecs{
|
||||
NumDataDisks: 0,
|
||||
Class: "performance1",
|
||||
DiskIOIndex: 0,
|
||||
PolicyClass: "performance_flavor",
|
||||
},
|
||||
}
|
||||
|
||||
// Performance2Flavor is the second result expected from parsing ListOutput.
|
||||
var Performance2Flavor = os.Flavor{
|
||||
var Performance2Flavor = Flavor{
|
||||
ID: "performance1-2",
|
||||
Disk: 40,
|
||||
RAM: 2048,
|
||||
@@ -122,8 +124,14 @@ var Performance2Flavor = os.Flavor{
|
||||
RxTxFactor: 400.0,
|
||||
Swap: 0,
|
||||
VCPUs: 2,
|
||||
ExtraSpecs: ExtraSpecs{
|
||||
NumDataDisks: 0,
|
||||
Class: "performance1",
|
||||
DiskIOIndex: 0,
|
||||
PolicyClass: "performance_flavor",
|
||||
},
|
||||
}
|
||||
|
||||
// ExpectedFlavorSlice is the slice of Flavor structs that are expected to be parsed from
|
||||
// ListOutput.
|
||||
var ExpectedFlavorSlice = []os.Flavor{Performance1Flavor, Performance2Flavor}
|
||||
var ExpectedFlavorSlice = []Flavor{Performance1Flavor, Performance2Flavor}
|
||||
|
||||
104
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/flavors/results.go
generated
vendored
Normal file
104
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/flavors/results.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
package flavors
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
os "github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ExtraSpecs provide additional information about the flavor.
|
||||
type ExtraSpecs struct {
|
||||
// The number of data disks
|
||||
NumDataDisks int `mapstructure:"number_of_data_disks"`
|
||||
// The flavor class
|
||||
Class string `mapstructure:"class"`
|
||||
// Relative measure of disk I/O performance from 0-99, where higher is faster
|
||||
DiskIOIndex int `mapstructure:"disk_io_index"`
|
||||
PolicyClass string `mapstructure:"policy_class"`
|
||||
}
|
||||
|
||||
// Flavor records represent (virtual) hardware configurations for server resources in a region.
|
||||
type Flavor struct {
|
||||
// The Id field contains the flavor's unique identifier.
|
||||
// For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance.
|
||||
ID string `mapstructure:"id"`
|
||||
|
||||
// The Disk and RA< fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
|
||||
Disk int `mapstructure:"disk"`
|
||||
RAM int `mapstructure:"ram"`
|
||||
|
||||
// The Name field provides a human-readable moniker for the flavor.
|
||||
Name string `mapstructure:"name"`
|
||||
|
||||
RxTxFactor float64 `mapstructure:"rxtx_factor"`
|
||||
|
||||
// Swap indicates how much space is reserved for swap.
|
||||
// If not provided, this field will be set to 0.
|
||||
Swap int `mapstructure:"swap"`
|
||||
|
||||
// VCPUs indicates how many (virtual) CPUs are available for this flavor.
|
||||
VCPUs int `mapstructure:"vcpus"`
|
||||
|
||||
// ExtraSpecs provides extra information about the flavor
|
||||
ExtraSpecs ExtraSpecs `mapstructure:"OS-FLV-WITH-EXT-SPECS:extra_specs"`
|
||||
}
|
||||
|
||||
// GetResult temporarily holds the response from a Get call.
|
||||
type GetResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract provides access to the individual Flavor returned by the Get function.
|
||||
func (gr GetResult) Extract() (*Flavor, error) {
|
||||
if gr.Err != nil {
|
||||
return nil, gr.Err
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Flavor Flavor `mapstructure:"flavor"`
|
||||
}
|
||||
|
||||
cfg := &mapstructure.DecoderConfig{
|
||||
DecodeHook: defaulter,
|
||||
Result: &result,
|
||||
}
|
||||
decoder, err := mapstructure.NewDecoder(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = decoder.Decode(gr.Body)
|
||||
return &result.Flavor, err
|
||||
}
|
||||
|
||||
func defaulter(from, to reflect.Kind, v interface{}) (interface{}, error) {
|
||||
if (from == reflect.String) && (to == reflect.Int) {
|
||||
return 0, nil
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation.
|
||||
func ExtractFlavors(page pagination.Page) ([]Flavor, error) {
|
||||
casted := page.(os.FlavorPage).Body
|
||||
var container struct {
|
||||
Flavors []Flavor `mapstructure:"flavors"`
|
||||
}
|
||||
|
||||
cfg := &mapstructure.DecoderConfig{
|
||||
DecodeHook: defaulter,
|
||||
Result: &container,
|
||||
}
|
||||
decoder, err := mapstructure.NewDecoder(cfg)
|
||||
if err != nil {
|
||||
return container.Flavors, err
|
||||
}
|
||||
err = decoder.Decode(casted)
|
||||
if err != nil {
|
||||
return container.Flavors, err
|
||||
}
|
||||
|
||||
return container.Flavors, nil
|
||||
}
|
||||
9
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/flavors/urls.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/compute/v2/flavors/urls.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package flavors
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
func getURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("flavors", id)
|
||||
}
|
||||
@@ -12,13 +12,24 @@ type CreateOpts struct {
|
||||
// Name [required] is the name to assign to the newly launched server.
|
||||
Name string
|
||||
|
||||
// ImageRef [required] is the ID or full URL to the image that contains the server's OS and initial state.
|
||||
// Optional if using the boot-from-volume extension.
|
||||
// ImageRef [optional; required if ImageName is not provided] is the ID or full
|
||||
// URL to the image that contains the server's OS and initial state.
|
||||
// Also optional if using the boot-from-volume extension.
|
||||
ImageRef string
|
||||
|
||||
// FlavorRef [required] is the ID or full URL to the flavor that describes the server's specs.
|
||||
// ImageName [optional; required if ImageRef is not provided] is the name of the
|
||||
// image that contains the server's OS and initial state.
|
||||
// Also optional if using the boot-from-volume extension.
|
||||
ImageName string
|
||||
|
||||
// FlavorRef [optional; required if FlavorName is not provided] is the ID or
|
||||
// full URL to the flavor that describes the server's specs.
|
||||
FlavorRef string
|
||||
|
||||
// FlavorName [optional; required if FlavorRef is not provided] is the name of
|
||||
// the flavor that describes the server's specs.
|
||||
FlavorName string
|
||||
|
||||
// SecurityGroups [optional] lists the names of the security groups to which this server should belong.
|
||||
SecurityGroups []string
|
||||
|
||||
@@ -36,9 +47,9 @@ type CreateOpts struct {
|
||||
// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
|
||||
Metadata map[string]string
|
||||
|
||||
// Personality [optional] includes the path and contents of a file to inject into the server at launch.
|
||||
// The maximum size of the file is 255 bytes (decoded).
|
||||
Personality []byte
|
||||
// Personality [optional] includes files to inject into the server at launch.
|
||||
// Create will base64-encode file contents for you.
|
||||
Personality os.Personality
|
||||
|
||||
// ConfigDrive [optional] enables metadata injection through a configuration drive.
|
||||
ConfigDrive bool
|
||||
@@ -58,7 +69,7 @@ type CreateOpts struct {
|
||||
DiskConfig diskconfig.DiskConfig
|
||||
|
||||
// BlockDevice [optional] will create the server from a volume, which is created from an image,
|
||||
// a snapshot, or an another volume.
|
||||
// a snapshot, or another volume.
|
||||
BlockDevice []bootfromvolume.BlockDevice
|
||||
}
|
||||
|
||||
@@ -68,7 +79,9 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
|
||||
base := os.CreateOpts{
|
||||
Name: opts.Name,
|
||||
ImageRef: opts.ImageRef,
|
||||
ImageName: opts.ImageName,
|
||||
FlavorRef: opts.FlavorRef,
|
||||
FlavorName: opts.FlavorName,
|
||||
SecurityGroups: opts.SecurityGroups,
|
||||
UserData: opts.UserData,
|
||||
AvailabilityZone: opts.AvailabilityZone,
|
||||
@@ -104,7 +117,9 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
|
||||
// key_name doesn't actually come from the extension (or at least isn't documented there) so
|
||||
// we need to add it manually.
|
||||
serverMap := res["server"].(map[string]interface{})
|
||||
if opts.KeyPair != "" {
|
||||
serverMap["key_name"] = opts.KeyPair
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@@ -130,9 +145,9 @@ type RebuildOpts struct {
|
||||
// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
|
||||
Metadata map[string]string
|
||||
|
||||
// Personality [optional] includes the path and contents of a file to inject into the server at launch.
|
||||
// The maximum size of the file is 255 bytes (decoded).
|
||||
Personality []byte
|
||||
// Personality [optional] includes files to inject into the server at launch.
|
||||
// Rebuild will base64-encode file contents for you.
|
||||
Personality os.Personality
|
||||
|
||||
// Rackspace-specific stuff begins here.
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ func TestCreateOpts(t *testing.T) {
|
||||
"name": "createdserver",
|
||||
"imageRef": "image-id",
|
||||
"flavorRef": "flavor-id",
|
||||
"flavorName": "",
|
||||
"imageName": "",
|
||||
"key_name": "mykey",
|
||||
"OS-DCF:diskConfig": "MANUAL"
|
||||
}
|
||||
|
||||
@@ -3,24 +3,53 @@ package volumeattach
|
||||
import (
|
||||
"testing"
|
||||
|
||||
os "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
|
||||
fixtures "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach/testing"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
"github.com/rackspace/gophercloud/testhelper/client"
|
||||
)
|
||||
|
||||
// FirstVolumeAttachment is the first result in ListOutput.
|
||||
var FirstVolumeAttachment = volumeattach.VolumeAttachment{
|
||||
Device: "/dev/vdd",
|
||||
ID: "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
||||
VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
}
|
||||
|
||||
// SecondVolumeAttachment is the first result in ListOutput.
|
||||
var SecondVolumeAttachment = volumeattach.VolumeAttachment{
|
||||
Device: "/dev/vdc",
|
||||
ID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
||||
VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
}
|
||||
|
||||
// ExpectedVolumeAttachmentSlide is the slice of results that should be parsed
|
||||
// from ListOutput, in the expected order.
|
||||
var ExpectedVolumeAttachmentSlice = []volumeattach.VolumeAttachment{FirstVolumeAttachment, SecondVolumeAttachment}
|
||||
|
||||
//CreatedVolumeAttachment is the parsed result from CreatedOutput.
|
||||
var CreatedVolumeAttachment = volumeattach.VolumeAttachment{
|
||||
Device: "/dev/vdc",
|
||||
ID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
||||
VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
os.HandleListSuccessfully(t)
|
||||
fixtures.HandleListSuccessfully(t)
|
||||
serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0"
|
||||
|
||||
count := 0
|
||||
err := List(client.ServiceClient(), serverId).EachPage(func(page pagination.Page) (bool, error) {
|
||||
count++
|
||||
actual, err := os.ExtractVolumeAttachments(page)
|
||||
actual, err := volumeattach.ExtractVolumeAttachments(page)
|
||||
th.AssertNoErr(t, err)
|
||||
th.CheckDeepEquals(t, os.ExpectedVolumeAttachmentSlice, actual)
|
||||
th.CheckDeepEquals(t, ExpectedVolumeAttachmentSlice, actual)
|
||||
|
||||
return true, nil
|
||||
})
|
||||
@@ -31,33 +60,33 @@ func TestList(t *testing.T) {
|
||||
func TestCreate(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
os.HandleCreateSuccessfully(t)
|
||||
fixtures.HandleCreateSuccessfully(t)
|
||||
serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0"
|
||||
|
||||
actual, err := Create(client.ServiceClient(), serverId, os.CreateOpts{
|
||||
actual, err := Create(client.ServiceClient(), serverId, volumeattach.CreateOpts{
|
||||
Device: "/dev/vdc",
|
||||
VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
}).Extract()
|
||||
th.AssertNoErr(t, err)
|
||||
th.CheckDeepEquals(t, &os.CreatedVolumeAttachment, actual)
|
||||
th.CheckDeepEquals(t, &CreatedVolumeAttachment, actual)
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
os.HandleGetSuccessfully(t)
|
||||
fixtures.HandleGetSuccessfully(t)
|
||||
aId := "a26887c6-c47b-4654-abb5-dfadf7d3f804"
|
||||
serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0"
|
||||
|
||||
actual, err := Get(client.ServiceClient(), serverId, aId).Extract()
|
||||
th.AssertNoErr(t, err)
|
||||
th.CheckDeepEquals(t, &os.SecondVolumeAttachment, actual)
|
||||
th.CheckDeepEquals(t, &SecondVolumeAttachment, actual)
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
os.HandleDeleteSuccessfully(t)
|
||||
fixtures.HandleDeleteSuccessfully(t)
|
||||
aId := "a26887c6-c47b-4654-abb5-dfadf7d3f804"
|
||||
serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0"
|
||||
|
||||
|
||||
36
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/fixtures.go
generated
vendored
36
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/lb/v1/nodes/fixtures.go
generated
vendored
@@ -107,6 +107,42 @@ func mockCreateResponse(t *testing.T, lbID int) {
|
||||
})
|
||||
}
|
||||
|
||||
func mockCreateErrResponse(t *testing.T, lbID int) {
|
||||
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "POST")
|
||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
||||
|
||||
th.TestJSONRequest(t, r, `
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"address": "10.2.2.3",
|
||||
"port": 80,
|
||||
"condition": "ENABLED",
|
||||
"type": "PRIMARY"
|
||||
},
|
||||
{
|
||||
"address": "10.2.2.4",
|
||||
"port": 81,
|
||||
"condition": "ENABLED",
|
||||
"type": "SECONDARY"
|
||||
}
|
||||
]
|
||||
}
|
||||
`)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(422) // Unprocessable Entity
|
||||
|
||||
fmt.Fprintf(w, `
|
||||
{
|
||||
"code": 422,
|
||||
"message": "Load Balancer '%d' has a status of 'PENDING_UPDATE' and is considered immutable."
|
||||
}
|
||||
`, lbID)
|
||||
})
|
||||
}
|
||||
|
||||
func mockBatchDeleteResponse(t *testing.T, lbID int, ids []int) {
|
||||
th.Mux.HandleFunc(_rootURL(lbID), func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "DELETE")
|
||||
|
||||
@@ -119,12 +119,7 @@ func Create(client *gophercloud.ServiceClient, loadBalancerID int, opts CreateOp
|
||||
return res
|
||||
}
|
||||
|
||||
pr, err := pagination.PageResultFrom(resp)
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
pr := pagination.PageResultFromParsed(resp, res.Body)
|
||||
return CreateResult{pagination.SinglePageBase(pr)}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,38 @@ func TestCreate(t *testing.T) {
|
||||
th.CheckDeepEquals(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestCreateErr(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
|
||||
mockCreateErrResponse(t, lbID)
|
||||
|
||||
opts := CreateOpts{
|
||||
CreateOpt{
|
||||
Address: "10.2.2.3",
|
||||
Port: 80,
|
||||
Condition: ENABLED,
|
||||
Type: PRIMARY,
|
||||
},
|
||||
CreateOpt{
|
||||
Address: "10.2.2.4",
|
||||
Port: 81,
|
||||
Condition: ENABLED,
|
||||
Type: SECONDARY,
|
||||
},
|
||||
}
|
||||
|
||||
page := Create(client.ServiceClient(), lbID, opts)
|
||||
|
||||
actual, err := page.ExtractNodes()
|
||||
if err == nil {
|
||||
t.Fatal("Did not receive expected error from ExtractNodes")
|
||||
}
|
||||
if actual != nil {
|
||||
t.Fatalf("Received non-nil result from failed ExtractNodes: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBulkDelete(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
|
||||
@@ -126,6 +126,9 @@ type CreateResult struct {
|
||||
|
||||
// ExtractNodes extracts a slice of Node structs from a CreateResult.
|
||||
func (res CreateResult) ExtractNodes() ([]Node, error) {
|
||||
if res.Err != nil {
|
||||
return nil, res.Err
|
||||
}
|
||||
return commonExtractNodes(res.Body)
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ func mockEnableResponse(t *testing.T, lbID int) {
|
||||
`)
|
||||
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
fmt.Fprintf(w, `{}`)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ func mockUpdateResponse(t *testing.T, lbID int) {
|
||||
`)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, `{}`)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ func mockCreateResponse(t *testing.T, lbID int) {
|
||||
`)
|
||||
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
fmt.Fprintf(w, `{}`)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -203,8 +203,17 @@ func TestCreateWithOptionalFields(t *testing.T) {
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprintf(w, `
|
||||
{
|
||||
"network": {
|
||||
"name": "sample_network",
|
||||
"admin_state_up": true,
|
||||
"shared": true,
|
||||
"tenant_id": "12345"
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
iTrue := true
|
||||
|
||||
@@ -43,7 +43,9 @@ func Delete(c *gophercloud.ServiceClient, opts DeleteOptsBuilder) DeleteResult {
|
||||
JSONBody: reqBody,
|
||||
JSONResponse: &res.Body,
|
||||
})
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -53,7 +53,9 @@ func Enable(c *gophercloud.ServiceClient, containerName string, opts EnableOptsB
|
||||
MoreHeaders: h,
|
||||
OkCodes: []int{201, 202, 204},
|
||||
})
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
@@ -66,7 +68,9 @@ func Get(c *gophercloud.ServiceClient, containerName string) GetResult {
|
||||
resp, err := c.Request("HEAD", getURL(c, containerName), gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 204},
|
||||
})
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
@@ -149,7 +153,9 @@ func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsB
|
||||
MoreHeaders: h,
|
||||
OkCodes: []int{202, 204},
|
||||
})
|
||||
if resp != nil {
|
||||
res.Header = resp.Header
|
||||
}
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func Download(c *gophercloud.ServiceClient, containerName, objectName string, op
|
||||
}
|
||||
|
||||
// Create is a function that creates a new object or replaces an existing object.
|
||||
func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.Reader, opts os.CreateOptsBuilder) os.CreateResult {
|
||||
func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.ReadSeeker, opts os.CreateOptsBuilder) os.CreateResult {
|
||||
return os.Create(c, containerName, objectName, content, opts)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package objects
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects"
|
||||
@@ -66,21 +66,23 @@ func TestListObjectNames(t *testing.T) {
|
||||
func TestCreateObject(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
os.HandleCreateTextObjectSuccessfully(t)
|
||||
|
||||
content := bytes.NewBufferString("Did gyre and gimble in the wabe")
|
||||
content := "Did gyre and gimble in the wabe"
|
||||
os.HandleCreateTextObjectSuccessfully(t, content)
|
||||
|
||||
options := &os.CreateOpts{ContentType: "text/plain"}
|
||||
res := Create(fake.ServiceClient(), "testContainer", "testObject", content, options)
|
||||
res := Create(fake.ServiceClient(), "testContainer", "testObject", strings.NewReader(content), options)
|
||||
th.AssertNoErr(t, res.Err)
|
||||
}
|
||||
|
||||
func TestCreateObjectWithoutContentType(t *testing.T) {
|
||||
th.SetupHTTP()
|
||||
defer th.TeardownHTTP()
|
||||
os.HandleCreateTypelessObjectSuccessfully(t)
|
||||
|
||||
content := bytes.NewBufferString("The sky was the color of television, tuned to a dead channel.")
|
||||
res := Create(fake.ServiceClient(), "testContainer", "testObject", content, &os.CreateOpts{})
|
||||
content := "The sky was the color of television, tuned to a dead channel."
|
||||
os.HandleCreateTypelessObjectSuccessfully(t, content)
|
||||
|
||||
res := Create(fake.ServiceClient(), "testContainer", "testObject", strings.NewReader(content), &os.CreateOpts{})
|
||||
th.AssertNoErr(t, res.Err)
|
||||
}
|
||||
|
||||
|
||||
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/results.go
generated
vendored
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/results.go
generated
vendored
@@ -113,6 +113,9 @@ func DecodeHeader(from, to interface{}) error {
|
||||
// RFC3339Milli describes a common time format used by some API responses.
|
||||
const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
|
||||
|
||||
// Time format used in cloud orchestration
|
||||
const STACK_TIME_FMT = "2006-01-02T15:04:05"
|
||||
|
||||
/*
|
||||
Link is an internal type to be used in packages of collection resources that are
|
||||
paginated in a certain way.
|
||||
|
||||
Reference in New Issue
Block a user