Skip to content
This repository was archived by the owner on Aug 1, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 42 additions & 11 deletions acceptance/rackspace/compute/v2/servers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,30 @@ import (

"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/diskconfig"
oskey "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
os "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/rackspace/compute/v2/keypairs"
"github.com/rackspace/gophercloud/rackspace/compute/v2/servers"
th "github.com/rackspace/gophercloud/testhelper"
)

func createServer(t *testing.T, client *gophercloud.ServiceClient) *os.Server {
if testing.Short(){
func createServerKeyPair(t *testing.T, client *gophercloud.ServiceClient) *oskey.KeyPair {
name := tools.RandomString("importedkey-", 8)
pubkey := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDlIQ3r+zd97kb9Hzmujd3V6pbO53eb3Go4q2E8iqVGWQfZTrFdL9KACJnqJIm9HmncfRkUTxE37hqeGCCv8uD+ZPmPiZG2E60OX1mGDjbbzAyReRwYWXgXHopggZTLak5k4mwZYaxwaufbVBDRn847e01lZnaXaszEToLM37NLw+uz29sl3TwYy2R0RGHPwPc160aWmdLjSyd1Nd4c9pvvOP/EoEuBjIC6NJJwg2Rvg9sjjx9jYj0QUgc8CqKLN25oMZ69kNJzlFylKRUoeeVr89txlR59yehJWk6Uw6lYFTdJmcmQOFVAJ12RMmS1hLWCM8UzAgtw+EDa0eqBxBDl smash@winter"

k, err := keypairs.Create(client, oskey.CreateOpts{
Name: name,
PublicKey: pubkey,
}).Extract()
th.AssertNoErr(t, err)

return k
}

func createServer(t *testing.T, client *gophercloud.ServiceClient, keyName string) *os.Server {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add in the testing.Short() bit here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added from your branch in the rebase 😉

if testing.Short() {
t.Skip("Skipping test that requires server creation in short mode.")
}

Expand All @@ -23,10 +39,12 @@ func createServer(t *testing.T, client *gophercloud.ServiceClient) *os.Server {

name := tools.RandomString("Gophercloud-", 8)
t.Logf("Creating server [%s].", name)
s, err := servers.Create(client, &os.CreateOpts{
Name: name,
ImageRef: options.imageID,
FlavorRef: options.flavorID,
s, err := servers.Create(client, &servers.CreateOpts{
Name: name,
ImageRef: options.imageID,
FlavorRef: options.flavorID,
KeyPair: keyName,
DiskConfig: diskconfig.Manual,
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Creating server.")
Expand Down Expand Up @@ -122,10 +140,11 @@ func rebuildServer(t *testing.T, client *gophercloud.ServiceClient, server *os.S
options, err := optionsFromEnv()
th.AssertNoErr(t, err)

opts := os.RebuildOpts{
Name: tools.RandomString("RenamedGopher", 16),
AdminPass: tools.MakeNewPassword(server.AdminPass),
ImageID: options.imageID,
opts := servers.RebuildOpts{
Name: tools.RandomString("RenamedGopher", 16),
AdminPass: tools.MakeNewPassword(server.AdminPass),
ImageID: options.imageID,
DiskConfig: diskconfig.Manual,
}
after, err := servers.Rebuild(client, server.ID, opts).Extract()
th.AssertNoErr(t, err)
Expand All @@ -147,11 +166,23 @@ func deleteServer(t *testing.T, client *gophercloud.ServiceClient, server *os.Se
t.Logf("Server deleted successfully.")
}

func deleteServerKeyPair(t *testing.T, client *gophercloud.ServiceClient, k *oskey.KeyPair) {
t.Logf("> keypairs.Delete")

err := keypairs.Delete(client, k.Name).Extract()
th.AssertNoErr(t, err)

t.Logf("Keypair deleted successfully.")
}

func TestServerOperations(t *testing.T) {
client, err := newClient()
th.AssertNoErr(t, err)

server := createServer(t, client)
kp := createServerKeyPair(t, client)
defer deleteServerKeyPair(t, client, kp)

server := createServer(t, client, kp.Name)
defer deleteServer(t, client, server)

getServer(t, client, server)
Expand Down
107 changes: 107 additions & 0 deletions openstack/compute/v2/extensions/diskconfig/requests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package diskconfig

import (
"errors"

"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
)

// DiskConfig represents one of the two possible settings for the DiskConfig option when creating,
// rebuilding, or resizing servers: Auto or Manual.
type DiskConfig string

const (
// Auto builds a server with a single partition the size of the target flavor disk and
// automatically adjusts the filesystem to fit the entire partition. Auto may only be used with
// images and servers that use a single EXT3 partition.
Auto DiskConfig = "AUTO"

// Manual builds a server using whatever partition scheme and filesystem are present in the source
// image. If the target flavor disk is larger, the remaining space is left unpartitioned. This
// enables images to have non-EXT3 filesystems, multiple partitions, and so on, and enables you
// to manage the disk configuration. It also results in slightly shorter boot times.
Manual DiskConfig = "MANUAL"
)

// ErrInvalidDiskConfig is returned if an invalid string is specified for a DiskConfig option.
var ErrInvalidDiskConfig = errors.New("DiskConfig must be either diskconfig.Auto or diskconfig.Manual.")

// Validate ensures that a DiskConfig contains an appropriate value.
func (config DiskConfig) validate() error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

switch config {
case Auto, Manual:
return nil
default:
return ErrInvalidDiskConfig
}
}

// CreateOptsExt adds a DiskConfig option to the base CreateOpts.
type CreateOptsExt struct {
servers.CreateOptsBuilder

// DiskConfig [optional] controls how the created server's disk is partitioned.
DiskConfig DiskConfig
}

// ToServerCreateMap adds the diskconfig option to the base server creation options.
func (opts CreateOptsExt) ToServerCreateMap() map[string]interface{} {
base := opts.CreateOptsBuilder.ToServerCreateMap()

serverMap := base["server"].(map[string]interface{})
serverMap["OS-DCF:diskConfig"] = string(opts.DiskConfig)

return base
}

// RebuildOptsExt adds a DiskConfig option to the base RebuildOpts.
type RebuildOptsExt struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great solution to this

servers.RebuildOptsBuilder

// DiskConfig [optional] controls how the rebuilt server's disk is partitioned.
DiskConfig DiskConfig
}

// ToServerRebuildMap adds the diskconfig option to the base server rebuild options.
func (opts RebuildOptsExt) ToServerRebuildMap() (map[string]interface{}, error) {
err := opts.DiskConfig.validate()
if err != nil {
return nil, err
}

base, err := opts.RebuildOptsBuilder.ToServerRebuildMap()
if err != nil {
return nil, err
}

serverMap := base["rebuild"].(map[string]interface{})
serverMap["OS-DCF:diskConfig"] = string(opts.DiskConfig)

return base, nil
}

// ResizeOptsExt adds a DiskConfig option to the base server resize options.
type ResizeOptsExt struct {
servers.ResizeOptsBuilder

// DiskConfig [optional] controls how the resized server's disk is partitioned.
DiskConfig DiskConfig
}

// ToServerResizeMap adds the diskconfig option to the base server creation options.
func (opts ResizeOptsExt) ToServerResizeMap() (map[string]interface{}, error) {
err := opts.DiskConfig.validate()
if err != nil {
return nil, err
}

base, err := opts.ResizeOptsBuilder.ToServerResizeMap()
if err != nil {
return nil, err
}

serverMap := base["resize"].(map[string]interface{})
serverMap["OS-DCF:diskConfig"] = string(opts.DiskConfig)

return base, nil
}
85 changes: 85 additions & 0 deletions openstack/compute/v2/extensions/diskconfig/requests_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package diskconfig

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",
}

ext := CreateOptsExt{
CreateOptsBuilder: base,
DiskConfig: Manual,
}

expected := `
{
"server": {
"name": "createdserver",
"imageRef": "asdfasdfasdf",
"flavorRef": "performance1-1",
"OS-DCF:diskConfig": "MANUAL"
}
}
`
th.CheckJSONEquals(t, expected, ext.ToServerCreateMap())
}

func TestRebuildOpts(t *testing.T) {
base := servers.RebuildOpts{
Name: "rebuiltserver",
AdminPass: "swordfish",
ImageID: "asdfasdfasdf",
}

ext := RebuildOptsExt{
RebuildOptsBuilder: base,
DiskConfig: Auto,
}

actual, err := ext.ToServerRebuildMap()
th.AssertNoErr(t, err)

expected := `
{
"rebuild": {
"name": "rebuiltserver",
"imageRef": "asdfasdfasdf",
"adminPass": "swordfish",
"OS-DCF:diskConfig": "AUTO"
}
}
`
th.CheckJSONEquals(t, expected, actual)
}

func TestResizeOpts(t *testing.T) {
base := servers.ResizeOpts{
FlavorRef: "performance1-8",
}

ext := ResizeOptsExt{
ResizeOptsBuilder: base,
DiskConfig: Auto,
}

actual, err := ext.ToServerResizeMap()
th.AssertNoErr(t, err)

expected := `
{
"resize": {
"flavorRef": "performance1-8",
"OS-DCF:diskConfig": "AUTO"
}
}
`
th.CheckJSONEquals(t, expected, actual)
}
60 changes: 60 additions & 0 deletions openstack/compute/v2/extensions/diskconfig/results.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package diskconfig

import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/pagination"
)

func commonExtract(result gophercloud.Result) (*DiskConfig, error) {
var resp struct {
Server struct {
DiskConfig string `mapstructure:"OS-DCF:diskConfig"`
} `mapstructure:"server"`
}

err := mapstructure.Decode(result.Body, &resp)
if err != nil {
return nil, err
}

config := DiskConfig(resp.Server.DiskConfig)
return &config, nil
}

// ExtractGet returns the disk configuration from a servers.Get call.
func ExtractGet(result servers.GetResult) (*DiskConfig, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In several of the other services, we just have Extract. I know that there is one that doesn't fit that mold (ExtractDiskConfig) but for consistency, we should either change those to reflect this way, or vice versa.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an extension, though. I was following @jamiehannaford's lead on naming here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, ok. I'm fine with having it like that. I'm just thinking that having 2 ways of writing an extraction will force the users to know if they're using an extension or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to agree. The logic is that you can perform one Get call, stash the GetResult, and use different Extract functions to pull data relevant to different extensions from it without having to make additional calls.

I think the extension mechanisms we're using need a little work, still - it's awkward to compose extensions and it's all a little too manual right now. I'm not sure what I'd rather see, though, and I think it'll need to wait for post-release. I'll try to articulate my thoughts about what I'd like to accomplish in a discussion issue, possibly after these next two weeks are over and we have 1.0 shipped 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Any changes can wait until a later release.

return commonExtract(result.Result)
}

// ExtractUpdate returns the disk configuration from a servers.Update call.
func ExtractUpdate(result servers.UpdateResult) (*DiskConfig, error) {
return commonExtract(result.Result)
}

// ExtractRebuild returns the disk configuration from a servers.Rebuild call.
func ExtractRebuild(result servers.RebuildResult) (*DiskConfig, error) {
return commonExtract(result.Result)
}

// ExtractDiskConfig returns the DiskConfig setting for a specific server acquired from an
// servers.ExtractServers call, while iterating through a Pager.
func ExtractDiskConfig(page pagination.Page, index int) (*DiskConfig, error) {
casted := page.(servers.ServerPage).Body

type server struct {
DiskConfig string `mapstructure:"OS-DCF:diskConfig"`
}
var response struct {
Servers []server `mapstructure:"servers"`
}

err := mapstructure.Decode(casted, &response)
if err != nil {
return nil, err
}

config := DiskConfig(response.Servers[index].DiskConfig)
return &config, nil
}
Loading