Skip to content
This repository was archived by the owner on Aug 1, 2023. It is now read-only.
50 changes: 50 additions & 0 deletions acceptance/openstack/compute/v2/bootfromvolume_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// +build acceptance

package v2

import (
"testing"

"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
th "github.com/rackspace/gophercloud/testhelper"
"github.com/smashwilson/gophercloud/acceptance/tools"
)

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

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

choices, err := ComputeChoicesFromEnv()
if err != nil {
t.Fatal(err)
}

name := tools.RandomString("Gophercloud-", 8)
t.Logf("Creating server [%s].", name)

bd := []bootfromvolume.BlockDevice{
bootfromvolume.BlockDevice{
UUID: choices.ImageID,
SourceType: bootfromvolume.Image,
VolumeSize: 10,
},
}

serverCreateOpts := servers.CreateOpts{
Name: name,
FlavorRef: "3",
}
server, err := bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
serverCreateOpts,
bd,
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created server: %+v\n", server)
//defer deleteServer(t, client, server)
t.Logf("Deleting server [%s]...", name)
}
46 changes: 46 additions & 0 deletions acceptance/rackspace/compute/v2/bootfromvolume_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// +build acceptance

package v2

import (
"testing"

osBFV "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
"github.com/rackspace/gophercloud/rackspace/compute/v2/bootfromvolume"
"github.com/rackspace/gophercloud/rackspace/compute/v2/servers"
th "github.com/rackspace/gophercloud/testhelper"
"github.com/smashwilson/gophercloud/acceptance/tools"
)

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

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

options, err := optionsFromEnv()
th.AssertNoErr(t, err)

name := tools.RandomString("Gophercloud-", 8)
t.Logf("Creating server [%s].", name)

bd := []osBFV.BlockDevice{
osBFV.BlockDevice{
UUID: options.imageID,
SourceType: osBFV.Image,
VolumeSize: 10,
},
}

server, err := bootfromvolume.Create(client, servers.CreateOpts{
Name: name,
FlavorRef: "performance1-1",
BlockDevice: bd,
}).Extract()
th.AssertNoErr(t, err)
t.Logf("Created server: %+v\n", server)
//defer deleteServer(t, client, server)
t.Logf("Deleting server [%s]...", name)
}
111 changes: 111 additions & 0 deletions openstack/compute/v2/extensions/bootfromvolume/requests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package bootfromvolume

import (
"errors"
"strconv"

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

"github.com/racker/perigee"
)

// SourceType represents the type of medium being used to create the volume.
type SourceType string

const (
Volume SourceType = "volume"
Snapshot SourceType = "snapshot"
Image SourceType = "image"
)

// BlockDevice is a structure with options for booting a server instance
// from a volume. The volume may be created from an image, snapshot, or another
// volume.
type BlockDevice struct {
// BootIndex [optional] is the boot index. It defaults to 0.
BootIndex int `json:"boot_index"`

// DeleteOnTermination [optional] specifies whether or not to delete the attached volume
// when the server is deleted. Defaults to `false`.
DeleteOnTermination bool `json:"delete_on_termination"`

// DestinationType [optional] is the type that gets created. Possible values are "volume"
// and "local".
DestinationType string `json:"destination_type"`

// SourceType [required] must be one of: "volume", "snapshot", "image".
SourceType SourceType `json:"source_type"`

// UUID [required] is the unique identifier for the volume, snapshot, or image (see above)
UUID string `json:"uuid"`

// VolumeSize [optional] is the size of the volume to create (in gigabytes).
VolumeSize int `json:"volume_size"`
}

// CreateOptsExt is a structure that extends the server `CreateOpts` structure
// by allowing for a block device mapping.
type CreateOptsExt struct {
servers.CreateOptsBuilder
BlockDevice []BlockDevice `json:"block_device_mapping_v2,omitempty"`
}

// ToServerCreateMap adds the block device mapping 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
}

if len(opts.BlockDevice) == 0 {
return nil, errors.New("Required fields UUID and SourceType not set.")
}

serverMap := base["server"].(map[string]interface{})

blockDevice := make([]map[string]interface{}, len(opts.BlockDevice))

for i, bd := range opts.BlockDevice {
if string(bd.SourceType) == "" {
return nil, errors.New("SourceType must be one of: volume, image, snapshot.")
}

blockDevice[i] = make(map[string]interface{})

blockDevice[i]["source_type"] = bd.SourceType
blockDevice[i]["boot_index"] = strconv.Itoa(bd.BootIndex)
blockDevice[i]["delete_on_termination"] = strconv.FormatBool(bd.DeleteOnTermination)
blockDevice[i]["volume_size"] = strconv.Itoa(bd.VolumeSize)
if bd.UUID != "" {
blockDevice[i]["uuid"] = bd.UUID
}
if bd.DestinationType != "" {
blockDevice[i]["destination_type"] = bd.DestinationType
}

}
serverMap["block_device_mapping_v2"] = blockDevice

return base, nil
}

// Create requests the creation of a server from the given block device mapping.
func Create(client *gophercloud.ServiceClient, opts servers.CreateOptsBuilder) servers.CreateResult {
var res servers.CreateResult

reqBody, err := opts.ToServerCreateMap()
if err != nil {
res.Err = err
return res
}

_, res.Err = perigee.Request("POST", createURL(client), perigee.Options{
MoreHeaders: client.AuthenticatedHeaders(),
ReqBody: reqBody,
Results: &res.Body,
OkCodes: []int{200, 202},
})
return res
}
51 changes: 51 additions & 0 deletions openstack/compute/v2/extensions/bootfromvolume/requests_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package bootfromvolume

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,
BlockDevice: []BlockDevice{
BlockDevice{
UUID: "123456",
SourceType: Image,
DestinationType: "volume",
VolumeSize: 10,
},
},
}

expected := `
{
"server": {
"name": "createdserver",
"imageRef": "asdfasdfasdf",
"flavorRef": "performance1-1",
"block_device_mapping_v2":[
{
"uuid":"123456",
"source_type":"image",
"destination_type":"volume",
"boot_index": "0",
"delete_on_termination": "false",
"volume_size": "10"
}
]
}
}
`
actual, err := ext.ToServerCreateMap()
th.AssertNoErr(t, err)
th.CheckJSONEquals(t, expected, actual)
}
10 changes: 10 additions & 0 deletions openstack/compute/v2/extensions/bootfromvolume/results.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package bootfromvolume

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

// CreateResult temporarily contains the response from a Create call.
type CreateResult struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, is there a reason for this type? If it doesn't have its own Extract method or anything, I just return os.CreateResult directly, instead.

os.CreateResult
}
7 changes: 7 additions & 0 deletions openstack/compute/v2/extensions/bootfromvolume/urls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package bootfromvolume

import "github.com/rackspace/gophercloud"

func createURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("os-volumes_boot")
}
16 changes: 16 additions & 0 deletions openstack/compute/v2/extensions/bootfromvolume/urls_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package bootfromvolume

import (
"testing"

th "github.com/rackspace/gophercloud/testhelper"
"github.com/rackspace/gophercloud/testhelper/client"
)

func TestCreateURL(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
c := client.ServiceClient()

th.CheckEquals(t, c.Endpoint+"os-volumes_boot", createURL(c))
}
15 changes: 11 additions & 4 deletions openstack/compute/v2/extensions/diskconfig/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,24 @@ type CreateOptsExt struct {
servers.CreateOptsBuilder

// DiskConfig [optional] controls how the created server's disk is partitioned.
DiskConfig DiskConfig
DiskConfig DiskConfig `json:"OS-DCF:diskConfig,omitempty"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍 💯 🙇

}

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

Choose a reason for hiding this comment

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

👍 💯 x2

base, err := opts.CreateOptsBuilder.ToServerCreateMap()
if err != nil {
return nil, err
}

if string(opts.DiskConfig) == "" {
return base, nil
}

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

return base
return base, nil
}

// RebuildOptsExt adds a DiskConfig option to the base RebuildOpts.
Expand Down
4 changes: 3 additions & 1 deletion openstack/compute/v2/extensions/diskconfig/requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ func TestCreateOpts(t *testing.T) {
}
}
`
th.CheckJSONEquals(t, expected, ext.ToServerCreateMap())
actual, err := ext.ToServerCreateMap()
th.AssertNoErr(t, err)
th.CheckJSONEquals(t, expected, actual)
}

func TestRebuildOpts(t *testing.T) {
Expand Down
23 changes: 15 additions & 8 deletions openstack/compute/v2/servers/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa
// CreateOptsBuilder describes struct types that can be accepted by the Create call.
// The CreateOpts struct in this package does.
type CreateOptsBuilder interface {
ToServerCreateMap() map[string]interface{}
ToServerCreateMap() (map[string]interface{}, error)
}

// Network is used within CreateOpts to control a new server's network attachments.
Expand Down Expand Up @@ -134,7 +134,7 @@ type CreateOpts struct {
}

// ToServerCreateMap assembles a request body based on the contents of a CreateOpts.
func (opts CreateOpts) ToServerCreateMap() map[string]interface{} {
func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
server := make(map[string]interface{})

server["name"] = opts.Name
Expand Down Expand Up @@ -183,19 +183,26 @@ func (opts CreateOpts) ToServerCreateMap() map[string]interface{} {
server["networks"] = networks
}

return map[string]interface{}{"server": server}
return map[string]interface{}{"server": server}, nil
}

// Create requests a server to be provisioned to the user in the current tenant.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var result CreateResult
_, result.Err = perigee.Request("POST", listURL(client), perigee.Options{
Results: &result.Body,
ReqBody: opts.ToServerCreateMap(),
var res CreateResult

reqBody, err := opts.ToServerCreateMap()
if err != nil {
res.Err = err
return res
}

_, res.Err = perigee.Request("POST", listURL(client), perigee.Options{
Results: &res.Body,
ReqBody: reqBody,
MoreHeaders: client.AuthenticatedHeaders(),
OkCodes: []int{202},
})
return result
return res
}

// Delete requests that a server previously provisioned be removed from your account.
Expand Down
Loading