Merge pull request #6596 from rickard-von-essen/openstack-cinder-root-volume
OpenStack: Block Storage volumes support
This commit is contained in:
commit
71c515fda1
|
@ -194,6 +194,13 @@ func (c *AccessConfig) imageV2Client() (*gophercloud.ServiceClient, error) {
|
|||
})
|
||||
}
|
||||
|
||||
func (c *AccessConfig) blockStorageV3Client() (*gophercloud.ServiceClient, error) {
|
||||
return openstack.NewBlockStorageV3(c.osClient, gophercloud.EndpointOpts{
|
||||
Region: c.Region,
|
||||
Availability: c.getEndpointType(),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AccessConfig) getEndpointType() gophercloud.Availability {
|
||||
if c.EndpointType == "internal" || c.EndpointType == "internalURL" {
|
||||
return gophercloud.AvailabilityInternal
|
||||
|
|
|
@ -86,6 +86,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
|
||||
SSHAgentAuth: b.config.RunConfig.Comm.SSHAgentAuth,
|
||||
},
|
||||
&StepCreateVolume{
|
||||
UseBlockStorageVolume: b.config.UseBlockStorageVolume,
|
||||
SourceImage: b.config.SourceImage,
|
||||
VolumeName: b.config.VolumeName,
|
||||
VolumeType: b.config.VolumeType,
|
||||
VolumeAvailabilityZone: b.config.VolumeAvailabilityZone,
|
||||
},
|
||||
&StepRunSourceServer{
|
||||
Name: b.config.InstanceName,
|
||||
SourceImage: b.config.SourceImage,
|
||||
|
@ -98,6 +105,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
UserDataFile: b.config.UserDataFile,
|
||||
ConfigDrive: b.config.ConfigDrive,
|
||||
InstanceMetadata: b.config.InstanceMetadata,
|
||||
UseBlockStorageVolume: b.config.UseBlockStorageVolume,
|
||||
},
|
||||
&StepGetPassword{
|
||||
Debug: b.config.PackerDebug,
|
||||
|
@ -124,7 +132,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
},
|
||||
&common.StepProvision{},
|
||||
&StepStopServer{},
|
||||
&stepCreateImage{},
|
||||
&StepDetachVolume{
|
||||
UseBlockStorageVolume: b.config.UseBlockStorageVolume,
|
||||
},
|
||||
&stepCreateImage{
|
||||
UseBlockStorageVolume: b.config.UseBlockStorageVolume,
|
||||
},
|
||||
&stepUpdateImageVisibility{},
|
||||
&stepAddImageMembers{},
|
||||
}
|
||||
|
|
|
@ -36,6 +36,11 @@ type RunConfig struct {
|
|||
|
||||
ConfigDrive bool `mapstructure:"config_drive"`
|
||||
|
||||
UseBlockStorageVolume bool `mapstructure:"use_blockstorage_volume"`
|
||||
VolumeName string `mapstructure:"volume_name"`
|
||||
VolumeType string `mapstructure:"volume_type"`
|
||||
VolumeAvailabilityZone string `mapstructure:"volume_availability_zone"`
|
||||
|
||||
// Not really used, but here for BC
|
||||
OpenstackProvider string `mapstructure:"openstack_provider"`
|
||||
UseFloatingIp bool `mapstructure:"use_floating_ip"`
|
||||
|
@ -90,5 +95,18 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
|||
}
|
||||
}
|
||||
|
||||
if c.UseBlockStorageVolume {
|
||||
// Use Compute instance availability zone for the Block Storage volume if
|
||||
// it's not provided.
|
||||
if c.VolumeAvailabilityZone == "" {
|
||||
c.VolumeAvailabilityZone = c.AvailabilityZone
|
||||
}
|
||||
|
||||
// Use random name for the Block Storage volume if it's not provided.
|
||||
if c.VolumeName == "" {
|
||||
c.VolumeName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
|
|
@ -70,3 +70,42 @@ func TestRunConfigPrepare_SSHPort(t *testing.T) {
|
|||
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_BlockStorage(t *testing.T) {
|
||||
c := testRunConfig()
|
||||
c.UseBlockStorageVolume = true
|
||||
c.VolumeType = "fast"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c.VolumeType != "fast" {
|
||||
t.Fatalf("invalid value: %s", c.VolumeType)
|
||||
}
|
||||
|
||||
c.AvailabilityZone = "RegionTwo"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.VolumeAvailabilityZone != "RegionTwo" {
|
||||
t.Fatalf("invalid value: %s", c.VolumeAvailabilityZone)
|
||||
}
|
||||
|
||||
c.VolumeAvailabilityZone = "RegionOne"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.VolumeAvailabilityZone != "RegionOne" {
|
||||
t.Fatalf("invalid value: %s", c.VolumeAvailabilityZone)
|
||||
}
|
||||
|
||||
c.VolumeName = "PackerVolume"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.VolumeName != "PackerVolume" {
|
||||
t.Fatalf("invalid value: %s", c.VolumeName)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
|
@ -13,7 +15,9 @@ import (
|
|||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepCreateImage struct{}
|
||||
type stepCreateImage struct {
|
||||
UseBlockStorageVolume bool
|
||||
}
|
||||
|
||||
func (s *stepCreateImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(Config)
|
||||
|
@ -28,9 +32,32 @@ func (s *stepCreateImage) Run(_ context.Context, state multistep.StateBag) multi
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Create the image
|
||||
// Create the image.
|
||||
// Image source depends on the type of the Compute instance. It can be
|
||||
// Block Storage service volume or regular Compute service local volume.
|
||||
ui.Say(fmt.Sprintf("Creating the image: %s", config.ImageName))
|
||||
imageId, err := servers.CreateImage(client, server.ID, servers.CreateImageOpts{
|
||||
var imageId string
|
||||
if s.UseBlockStorageVolume {
|
||||
// We need the v3 block storage client.
|
||||
blockStorageClient, err := config.blockStorageV3Client()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing block storage client: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
volume := state.Get("volume_id").(string)
|
||||
image, err := volumeactions.UploadImage(blockStorageClient, volume, volumeactions.UploadImageOpts{
|
||||
ImageName: config.ImageName,
|
||||
}).Extract()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
imageId = image.ImageID
|
||||
} else {
|
||||
imageId, err = servers.CreateImage(client, server.ID, servers.CreateImageOpts{
|
||||
Name: config.ImageName,
|
||||
Metadata: config.ImageMetadata,
|
||||
}).ExtractImageID()
|
||||
|
@ -40,6 +67,7 @@ func (s *stepCreateImage) Run(_ context.Context, state multistep.StateBag) multi
|
|||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
// Set the Image ID in the state
|
||||
ui.Message(fmt.Sprintf("Image: %s", imageId))
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepCreateVolume struct {
|
||||
UseBlockStorageVolume bool
|
||||
SourceImage string
|
||||
VolumeName string
|
||||
VolumeType string
|
||||
VolumeAvailabilityZone string
|
||||
volumeID string
|
||||
doCleanup bool
|
||||
}
|
||||
|
||||
func (s *StepCreateVolume) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
// Proceed only if block storage volume is required.
|
||||
if !s.UseBlockStorageVolume {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// We will need Block Storage and Image services clients.
|
||||
blockStorageClient, err := config.blockStorageV3Client()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing block storage client: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
imageClient, err := config.imageV2Client()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing image client: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Get needed volume size from the source image.
|
||||
volumeSize, err := GetVolumeSize(imageClient, s.SourceImage)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating volume: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Creating volume...")
|
||||
volumeOpts := volumes.CreateOpts{
|
||||
Size: volumeSize,
|
||||
VolumeType: s.VolumeType,
|
||||
AvailabilityZone: s.VolumeAvailabilityZone,
|
||||
Name: s.VolumeName,
|
||||
ImageID: s.SourceImage,
|
||||
}
|
||||
volume, err := volumes.Create(blockStorageClient, volumeOpts).Extract()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating volume: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Wait for volume to become available.
|
||||
ui.Say(fmt.Sprintf("Waiting for volume %s (volume id: %s) to become available...", config.VolumeName, volume.ID))
|
||||
if err := WaitForVolume(blockStorageClient, volume.ID); err != nil {
|
||||
err := fmt.Errorf("Error waiting for volume: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Volume was created, so remember to clean it up.
|
||||
s.doCleanup = true
|
||||
|
||||
// Set the Volume ID in the state.
|
||||
ui.Message(fmt.Sprintf("Volume ID: %s", volume.ID))
|
||||
state.Put("volume_id", volume.ID)
|
||||
s.volumeID = volume.ID
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
|
||||
if !s.doCleanup {
|
||||
return
|
||||
}
|
||||
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
blockStorageClient, err := config.blockStorageV3Client()
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error cleaning up volume. Please delete the volume manually: %s", s.volumeID))
|
||||
return
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Deleting volume: %s ...", s.volumeID))
|
||||
err = volumes.Delete(blockStorageClient, s.volumeID).ExtractErr()
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error cleaning up volume. Please delete the volume manually: %s", s.volumeID))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepDetachVolume struct {
|
||||
UseBlockStorageVolume bool
|
||||
}
|
||||
|
||||
func (s *StepDetachVolume) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
// Proceed only if block storage volume is used.
|
||||
if !s.UseBlockStorageVolume {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
blockStorageClient, err := config.blockStorageV3Client()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing block storage client: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
volume := state.Get("volume_id").(string)
|
||||
ui.Say(fmt.Sprintf("Detaching volume %s (volume id: %s)", config.VolumeName, volume))
|
||||
if err := volumeactions.Detach(blockStorageClient, volume, volumeactions.DetachOpts{}).ExtractErr(); err != nil {
|
||||
err = fmt.Errorf("Error detaching block storage volume: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Wait for volume to become available.
|
||||
ui.Say(fmt.Sprintf("Waiting for volume %s (volume id: %s) to become available...", config.VolumeName, volume))
|
||||
if err := WaitForVolume(blockStorageClient, volume); err != nil {
|
||||
err := fmt.Errorf("Error waiting for volume: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepDetachVolume) Cleanup(multistep.StateBag) {
|
||||
// No cleanup.
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
|
@ -24,6 +25,7 @@ type StepRunSourceServer struct {
|
|||
UserDataFile string
|
||||
ConfigDrive bool
|
||||
InstanceMetadata map[string]string
|
||||
UseBlockStorageVolume bool
|
||||
server *servers.Server
|
||||
}
|
||||
|
||||
|
@ -74,18 +76,40 @@ func (s *StepRunSourceServer) Run(_ context.Context, state multistep.StateBag) m
|
|||
ServiceClient: computeClient,
|
||||
Metadata: s.InstanceMetadata,
|
||||
}
|
||||
|
||||
var serverOptsExt servers.CreateOptsBuilder
|
||||
keyName, hasKey := state.GetOk("keyPair")
|
||||
if hasKey {
|
||||
serverOptsExt = keypairs.CreateOptsExt{
|
||||
|
||||
// Create root volume in the Block Storage service if required.
|
||||
// Add block device mapping v2 to the server create options if required.
|
||||
if s.UseBlockStorageVolume {
|
||||
volume := state.Get("volume_id").(string)
|
||||
blockDeviceMappingV2 := []bootfromvolume.BlockDevice{
|
||||
{
|
||||
BootIndex: 0,
|
||||
DestinationType: bootfromvolume.DestinationVolume,
|
||||
SourceType: bootfromvolume.SourceVolume,
|
||||
UUID: volume,
|
||||
},
|
||||
}
|
||||
// ImageRef and block device mapping is an invalid options combination.
|
||||
serverOpts.ImageRef = ""
|
||||
serverOptsExt = bootfromvolume.CreateOptsExt{
|
||||
CreateOptsBuilder: serverOpts,
|
||||
KeyName: keyName.(string),
|
||||
BlockDevice: blockDeviceMappingV2,
|
||||
}
|
||||
} else {
|
||||
serverOptsExt = serverOpts
|
||||
}
|
||||
|
||||
// Add keypair to the server create options.
|
||||
keyName, hasKey := state.GetOk("keyPair")
|
||||
if hasKey {
|
||||
serverOptsExt = keypairs.CreateOptsExt{
|
||||
CreateOptsBuilder: serverOptsExt,
|
||||
KeyName: keyName.(string),
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say("Launching server...")
|
||||
s.server, err = servers.Create(computeClient, serverOptsExt).Extract()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error launching source server: %s", err)
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
|
||||
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
|
||||
)
|
||||
|
||||
// WaitForVolume waits for the given volume to become available.
|
||||
func WaitForVolume(blockStorageClient *gophercloud.ServiceClient, volumeID string) error {
|
||||
maxNumErrors := 10
|
||||
numErrors := 0
|
||||
|
||||
for {
|
||||
volume, err := volumes.Get(blockStorageClient, volumeID).Extract()
|
||||
if err != nil {
|
||||
errCode, ok := err.(*gophercloud.ErrUnexpectedResponseCode)
|
||||
if ok && (errCode.Actual == 500 || errCode.Actual == 404) {
|
||||
numErrors++
|
||||
if numErrors >= maxNumErrors {
|
||||
log.Printf("[ERROR] Maximum number of errors (%d) reached; failing with: %s", numErrors, err)
|
||||
return err
|
||||
}
|
||||
log.Printf("[ERROR] %d error received, will ignore and retry: %s", errCode.Actual, err)
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if volume.Status == "available" {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Waiting for volume creation status: %s", volume.Status)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// GetVolumeSize returns volume size in gigabytes based on the image min disk
|
||||
// value if it's not empty.
|
||||
// Or it calculates needed gigabytes size from the image bytes size.
|
||||
func GetVolumeSize(imageClient *gophercloud.ServiceClient, imageID string) (int, error) {
|
||||
sourceImage, err := images.Get(imageClient, imageID).Extract()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if sourceImage.MinDiskGigabytes != 0 {
|
||||
return sourceImage.MinDiskGigabytes, nil
|
||||
}
|
||||
|
||||
volumeSizeMB := sourceImage.SizeBytes / 1024 / 1024
|
||||
volumeSizeGB := int(sourceImage.SizeBytes / 1024 / 1024 / 1024)
|
||||
|
||||
// Increment gigabytes size if the initial size can't be divided without
|
||||
// remainder.
|
||||
if volumeSizeMB%1024 > 0 {
|
||||
volumeSizeGB++
|
||||
}
|
||||
|
||||
return volumeSizeGB, nil
|
||||
}
|
86
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go
generated
vendored
Normal file
86
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
Package volumeactions provides information and interaction with volumes in the
|
||||
OpenStack Block Storage service. A volume is a detachable block storage
|
||||
device, akin to a USB hard drive.
|
||||
|
||||
Example of Attaching a Volume to an Instance
|
||||
|
||||
attachOpts := volumeactions.AttachOpts{
|
||||
MountPoint: "/mnt",
|
||||
Mode: "rw",
|
||||
InstanceUUID: server.ID,
|
||||
}
|
||||
|
||||
err := volumeactions.Attach(client, volume.ID, attachOpts).ExtractErr()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
detachOpts := volumeactions.DetachOpts{
|
||||
AttachmentID: volume.Attachments[0].AttachmentID,
|
||||
}
|
||||
|
||||
err = volumeactions.Detach(client, volume.ID, detachOpts).ExtractErr()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
|
||||
Example of Creating an Image from a Volume
|
||||
|
||||
uploadImageOpts := volumeactions.UploadImageOpts{
|
||||
ImageName: "my_vol",
|
||||
Force: true,
|
||||
}
|
||||
|
||||
volumeImage, err := volumeactions.UploadImage(client, volume.ID, uploadImageOpts).Extract()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", volumeImage)
|
||||
|
||||
Example of Extending a Volume's Size
|
||||
|
||||
extendOpts := volumeactions.ExtendSizeOpts{
|
||||
NewSize: 100,
|
||||
}
|
||||
|
||||
err := volumeactions.ExtendSize(client, volume.ID, extendOpts).ExtractErr()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Example of Initializing a Volume Connection
|
||||
|
||||
connectOpts := &volumeactions.InitializeConnectionOpts{
|
||||
IP: "127.0.0.1",
|
||||
Host: "stack",
|
||||
Initiator: "iqn.1994-05.com.redhat:17cf566367d2",
|
||||
Multipath: gophercloud.Disabled,
|
||||
Platform: "x86_64",
|
||||
OSType: "linux2",
|
||||
}
|
||||
|
||||
connectionInfo, err := volumeactions.InitializeConnection(client, volume.ID, connectOpts).Extract()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", connectionInfo["data"])
|
||||
|
||||
terminateOpts := &volumeactions.InitializeConnectionOpts{
|
||||
IP: "127.0.0.1",
|
||||
Host: "stack",
|
||||
Initiator: "iqn.1994-05.com.redhat:17cf566367d2",
|
||||
Multipath: gophercloud.Disabled,
|
||||
Platform: "x86_64",
|
||||
OSType: "linux2",
|
||||
}
|
||||
|
||||
err = volumeactions.TerminateConnection(client, volume.ID, terminateOpts).ExtractErr()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
*/
|
||||
package volumeactions
|
269
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go
generated
vendored
Normal file
269
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,269 @@
|
|||
package volumeactions
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// AttachOptsBuilder allows extensions to add additional parameters to the
|
||||
// Attach request.
|
||||
type AttachOptsBuilder interface {
|
||||
ToVolumeAttachMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// AttachMode describes the attachment mode for volumes.
|
||||
type AttachMode string
|
||||
|
||||
// These constants determine how a volume is attached.
|
||||
const (
|
||||
ReadOnly AttachMode = "ro"
|
||||
ReadWrite AttachMode = "rw"
|
||||
)
|
||||
|
||||
// AttachOpts contains options for attaching a Volume.
|
||||
type AttachOpts struct {
|
||||
// The mountpoint of this volume.
|
||||
MountPoint string `json:"mountpoint,omitempty"`
|
||||
|
||||
// The nova instance ID, can't set simultaneously with HostName.
|
||||
InstanceUUID string `json:"instance_uuid,omitempty"`
|
||||
|
||||
// The hostname of baremetal host, can't set simultaneously with InstanceUUID.
|
||||
HostName string `json:"host_name,omitempty"`
|
||||
|
||||
// Mount mode of this volume.
|
||||
Mode AttachMode `json:"mode,omitempty"`
|
||||
}
|
||||
|
||||
// ToVolumeAttachMap assembles a request body based on the contents of a
|
||||
// AttachOpts.
|
||||
func (opts AttachOpts) ToVolumeAttachMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "os-attach")
|
||||
}
|
||||
|
||||
// Attach will attach a volume based on the values in AttachOpts.
|
||||
func Attach(client *gophercloud.ServiceClient, id string, opts AttachOptsBuilder) (r AttachResult) {
|
||||
b, err := opts.ToVolumeAttachMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// BeginDetach will mark the volume as detaching.
|
||||
func BeginDetaching(client *gophercloud.ServiceClient, id string) (r BeginDetachingResult) {
|
||||
b := map[string]interface{}{"os-begin_detaching": make(map[string]interface{})}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// DetachOptsBuilder allows extensions to add additional parameters to the
|
||||
// Detach request.
|
||||
type DetachOptsBuilder interface {
|
||||
ToVolumeDetachMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// DetachOpts contains options for detaching a Volume.
|
||||
type DetachOpts struct {
|
||||
// AttachmentID is the ID of the attachment between a volume and instance.
|
||||
AttachmentID string `json:"attachment_id,omitempty"`
|
||||
}
|
||||
|
||||
// ToVolumeDetachMap assembles a request body based on the contents of a
|
||||
// DetachOpts.
|
||||
func (opts DetachOpts) ToVolumeDetachMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "os-detach")
|
||||
}
|
||||
|
||||
// Detach will detach a volume based on volume ID.
|
||||
func Detach(client *gophercloud.ServiceClient, id string, opts DetachOptsBuilder) (r DetachResult) {
|
||||
b, err := opts.ToVolumeDetachMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Reserve will reserve a volume based on volume ID.
|
||||
func Reserve(client *gophercloud.ServiceClient, id string) (r ReserveResult) {
|
||||
b := map[string]interface{}{"os-reserve": make(map[string]interface{})}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 201, 202},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Unreserve will unreserve a volume based on volume ID.
|
||||
func Unreserve(client *gophercloud.ServiceClient, id string) (r UnreserveResult) {
|
||||
b := map[string]interface{}{"os-unreserve": make(map[string]interface{})}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 201, 202},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// InitializeConnectionOptsBuilder allows extensions to add additional parameters to the
|
||||
// InitializeConnection request.
|
||||
type InitializeConnectionOptsBuilder interface {
|
||||
ToVolumeInitializeConnectionMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// InitializeConnectionOpts hosts options for InitializeConnection.
|
||||
// The fields are specific to the storage driver in use and the destination
|
||||
// attachment.
|
||||
type InitializeConnectionOpts struct {
|
||||
IP string `json:"ip,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Initiator string `json:"initiator,omitempty"`
|
||||
Wwpns []string `json:"wwpns,omitempty"`
|
||||
Wwnns string `json:"wwnns,omitempty"`
|
||||
Multipath *bool `json:"multipath,omitempty"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
OSType string `json:"os_type,omitempty"`
|
||||
}
|
||||
|
||||
// ToVolumeInitializeConnectionMap assembles a request body based on the contents of a
|
||||
// InitializeConnectionOpts.
|
||||
func (opts InitializeConnectionOpts) ToVolumeInitializeConnectionMap() (map[string]interface{}, error) {
|
||||
b, err := gophercloud.BuildRequestBody(opts, "connector")
|
||||
return map[string]interface{}{"os-initialize_connection": b}, err
|
||||
}
|
||||
|
||||
// InitializeConnection initializes an iSCSI connection by volume ID.
|
||||
func InitializeConnection(client *gophercloud.ServiceClient, id string, opts InitializeConnectionOptsBuilder) (r InitializeConnectionResult) {
|
||||
b, err := opts.ToVolumeInitializeConnectionMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 201, 202},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// TerminateConnectionOptsBuilder allows extensions to add additional parameters to the
|
||||
// TerminateConnection request.
|
||||
type TerminateConnectionOptsBuilder interface {
|
||||
ToVolumeTerminateConnectionMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// TerminateConnectionOpts hosts options for TerminateConnection.
|
||||
type TerminateConnectionOpts struct {
|
||||
IP string `json:"ip,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Initiator string `json:"initiator,omitempty"`
|
||||
Wwpns []string `json:"wwpns,omitempty"`
|
||||
Wwnns string `json:"wwnns,omitempty"`
|
||||
Multipath *bool `json:"multipath,omitempty"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
OSType string `json:"os_type,omitempty"`
|
||||
}
|
||||
|
||||
// ToVolumeTerminateConnectionMap assembles a request body based on the contents of a
|
||||
// TerminateConnectionOpts.
|
||||
func (opts TerminateConnectionOpts) ToVolumeTerminateConnectionMap() (map[string]interface{}, error) {
|
||||
b, err := gophercloud.BuildRequestBody(opts, "connector")
|
||||
return map[string]interface{}{"os-terminate_connection": b}, err
|
||||
}
|
||||
|
||||
// TerminateConnection terminates an iSCSI connection by volume ID.
|
||||
func TerminateConnection(client *gophercloud.ServiceClient, id string, opts TerminateConnectionOptsBuilder) (r TerminateConnectionResult) {
|
||||
b, err := opts.ToVolumeTerminateConnectionMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ExtendSizeOptsBuilder allows extensions to add additional parameters to the
|
||||
// ExtendSize request.
|
||||
type ExtendSizeOptsBuilder interface {
|
||||
ToVolumeExtendSizeMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// ExtendSizeOpts contains options for extending the size of an existing Volume.
|
||||
// This object is passed to the volumes.ExtendSize function.
|
||||
type ExtendSizeOpts struct {
|
||||
// NewSize is the new size of the volume, in GB.
|
||||
NewSize int `json:"new_size" required:"true"`
|
||||
}
|
||||
|
||||
// ToVolumeExtendSizeMap assembles a request body based on the contents of an
|
||||
// ExtendSizeOpts.
|
||||
func (opts ExtendSizeOpts) ToVolumeExtendSizeMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "os-extend")
|
||||
}
|
||||
|
||||
// ExtendSize will extend the size of the volume based on the provided information.
|
||||
// This operation does not return a response body.
|
||||
func ExtendSize(client *gophercloud.ServiceClient, id string, opts ExtendSizeOptsBuilder) (r ExtendSizeResult) {
|
||||
b, err := opts.ToVolumeExtendSizeMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// UploadImageOptsBuilder allows extensions to add additional parameters to the
|
||||
// UploadImage request.
|
||||
type UploadImageOptsBuilder interface {
|
||||
ToVolumeUploadImageMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// UploadImageOpts contains options for uploading a Volume to image storage.
|
||||
type UploadImageOpts struct {
|
||||
// Container format, may be bare, ofv, ova, etc.
|
||||
ContainerFormat string `json:"container_format,omitempty"`
|
||||
|
||||
// Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc.
|
||||
DiskFormat string `json:"disk_format,omitempty"`
|
||||
|
||||
// The name of image that will be stored in glance.
|
||||
ImageName string `json:"image_name,omitempty"`
|
||||
|
||||
// Force image creation, usable if volume attached to instance.
|
||||
Force bool `json:"force,omitempty"`
|
||||
}
|
||||
|
||||
// ToVolumeUploadImageMap assembles a request body based on the contents of a
|
||||
// UploadImageOpts.
|
||||
func (opts UploadImageOpts) ToVolumeUploadImageMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "os-volume_upload_image")
|
||||
}
|
||||
|
||||
// UploadImage will upload an image based on the values in UploadImageOptsBuilder.
|
||||
func UploadImage(client *gophercloud.ServiceClient, id string, opts UploadImageOptsBuilder) (r UploadImageResult) {
|
||||
b, err := opts.ToVolumeUploadImageMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ForceDelete will delete the volume regardless of state.
|
||||
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ForceDeleteResult) {
|
||||
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-force_delete": ""}, nil, nil)
|
||||
return
|
||||
}
|
191
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go
generated
vendored
Normal file
191
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go
generated
vendored
Normal file
|
@ -0,0 +1,191 @@
|
|||
package volumeactions
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// AttachResult contains the response body and error from an Attach request.
|
||||
type AttachResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// BeginDetachingResult contains the response body and error from a BeginDetach
|
||||
// request.
|
||||
type BeginDetachingResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// DetachResult contains the response body and error from a Detach request.
|
||||
type DetachResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// UploadImageResult contains the response body and error from an UploadImage
|
||||
// request.
|
||||
type UploadImageResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// ReserveResult contains the response body and error from a Reserve request.
|
||||
type ReserveResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// UnreserveResult contains the response body and error from an Unreserve
|
||||
// request.
|
||||
type UnreserveResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// TerminateConnectionResult contains the response body and error from a
|
||||
// TerminateConnection request.
|
||||
type TerminateConnectionResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// InitializeConnectionResult contains the response body and error from an
|
||||
// InitializeConnection request.
|
||||
type InitializeConnectionResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// ExtendSizeResult contains the response body and error from an ExtendSize request.
|
||||
type ExtendSizeResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// Extract will get the connection information out of the
|
||||
// InitializeConnectionResult object.
|
||||
//
|
||||
// This will be a generic map[string]interface{} and the results will be
|
||||
// dependent on the type of connection made.
|
||||
func (r InitializeConnectionResult) Extract() (map[string]interface{}, error) {
|
||||
var s struct {
|
||||
ConnectionInfo map[string]interface{} `json:"connection_info"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.ConnectionInfo, err
|
||||
}
|
||||
|
||||
// ImageVolumeType contains volume type information obtained from UploadImage
|
||||
// action.
|
||||
type ImageVolumeType struct {
|
||||
// The ID of a volume type.
|
||||
ID string `json:"id"`
|
||||
|
||||
// Human-readable display name for the volume type.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Human-readable description for the volume type.
|
||||
Description string `json:"display_description"`
|
||||
|
||||
// Flag for public access.
|
||||
IsPublic bool `json:"is_public"`
|
||||
|
||||
// Extra specifications for volume type.
|
||||
ExtraSpecs map[string]interface{} `json:"extra_specs"`
|
||||
|
||||
// ID of quality of service specs.
|
||||
QosSpecsID string `json:"qos_specs_id"`
|
||||
|
||||
// Flag for deletion status of volume type.
|
||||
Deleted bool `json:"deleted"`
|
||||
|
||||
// The date when volume type was deleted.
|
||||
DeletedAt time.Time `json:"-"`
|
||||
|
||||
// The date when volume type was created.
|
||||
CreatedAt time.Time `json:"-"`
|
||||
|
||||
// The date when this volume was last updated.
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
}
|
||||
|
||||
func (r *ImageVolumeType) UnmarshalJSON(b []byte) error {
|
||||
type tmp ImageVolumeType
|
||||
var s struct {
|
||||
tmp
|
||||
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
|
||||
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
|
||||
DeletedAt gophercloud.JSONRFC3339MilliNoZ `json:"deleted_at"`
|
||||
}
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*r = ImageVolumeType(s.tmp)
|
||||
|
||||
r.CreatedAt = time.Time(s.CreatedAt)
|
||||
r.UpdatedAt = time.Time(s.UpdatedAt)
|
||||
r.DeletedAt = time.Time(s.DeletedAt)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// VolumeImage contains information about volume uploaded to an image service.
|
||||
type VolumeImage struct {
|
||||
// The ID of a volume an image is created from.
|
||||
VolumeID string `json:"id"`
|
||||
|
||||
// Container format, may be bare, ofv, ova, etc.
|
||||
ContainerFormat string `json:"container_format"`
|
||||
|
||||
// Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc.
|
||||
DiskFormat string `json:"disk_format"`
|
||||
|
||||
// Human-readable description for the volume.
|
||||
Description string `json:"display_description"`
|
||||
|
||||
// The ID of the created image.
|
||||
ImageID string `json:"image_id"`
|
||||
|
||||
// Human-readable display name for the image.
|
||||
ImageName string `json:"image_name"`
|
||||
|
||||
// Size of the volume in GB.
|
||||
Size int `json:"size"`
|
||||
|
||||
// Current status of the volume.
|
||||
Status string `json:"status"`
|
||||
|
||||
// The date when this volume was last updated.
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
|
||||
// Volume type object of used volume.
|
||||
VolumeType ImageVolumeType `json:"volume_type"`
|
||||
}
|
||||
|
||||
func (r *VolumeImage) UnmarshalJSON(b []byte) error {
|
||||
type tmp VolumeImage
|
||||
var s struct {
|
||||
tmp
|
||||
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
|
||||
}
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*r = VolumeImage(s.tmp)
|
||||
|
||||
r.UpdatedAt = time.Time(s.UpdatedAt)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Extract will get an object with info about the uploaded image out of the
|
||||
// UploadImageResult object.
|
||||
func (r UploadImageResult) Extract() (VolumeImage, error) {
|
||||
var s struct {
|
||||
VolumeImage VolumeImage `json:"os-volume_upload_image"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.VolumeImage, err
|
||||
}
|
||||
|
||||
// ForceDeleteResult contains the response body and error from a ForceDelete request.
|
||||
type ForceDeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
7
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package volumeactions
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func actionURL(c *gophercloud.ServiceClient, id string) string {
|
||||
return c.ServiceURL("volumes", id, "action")
|
||||
}
|
5
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go
generated
vendored
Normal file
5
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
// Package volumes provides information and interaction with volumes in the
|
||||
// OpenStack Block Storage service. A volume is a detachable block storage
|
||||
// device, akin to a USB hard drive. It can only be attached to one instance at
|
||||
// a time.
|
||||
package volumes
|
202
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go
generated
vendored
Normal file
202
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
|||
package volumes
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// CreateOptsBuilder allows extensions to add additional parameters to the
|
||||
// Create request.
|
||||
type CreateOptsBuilder interface {
|
||||
ToVolumeCreateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// CreateOpts contains options for creating a Volume. This object is passed to
|
||||
// the volumes.Create function. For more information about these parameters,
|
||||
// see the Volume object.
|
||||
type CreateOpts struct {
|
||||
// The size of the volume, in GB
|
||||
Size int `json:"size" required:"true"`
|
||||
// The availability zone
|
||||
AvailabilityZone string `json:"availability_zone,omitempty"`
|
||||
// ConsistencyGroupID is the ID of a consistency group
|
||||
ConsistencyGroupID string `json:"consistencygroup_id,omitempty"`
|
||||
// The volume description
|
||||
Description string `json:"description,omitempty"`
|
||||
// One or more metadata key and value pairs to associate with the volume
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
// The volume name
|
||||
Name string `json:"name,omitempty"`
|
||||
// the ID of the existing volume snapshot
|
||||
SnapshotID string `json:"snapshot_id,omitempty"`
|
||||
// SourceReplica is a UUID of an existing volume to replicate with
|
||||
SourceReplica string `json:"source_replica,omitempty"`
|
||||
// the ID of the existing volume
|
||||
SourceVolID string `json:"source_volid,omitempty"`
|
||||
// The ID of the image from which you want to create the volume.
|
||||
// Required to create a bootable volume.
|
||||
ImageID string `json:"imageRef,omitempty"`
|
||||
// The associated volume type
|
||||
VolumeType string `json:"volume_type,omitempty"`
|
||||
}
|
||||
|
||||
// ToVolumeCreateMap assembles a request body based on the contents of a
|
||||
// CreateOpts.
|
||||
func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "volume")
|
||||
}
|
||||
|
||||
// Create will create a new Volume based on the values in CreateOpts. To extract
|
||||
// the Volume object from the response, call the Extract method on the
|
||||
// CreateResult.
|
||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
|
||||
b, err := opts.ToVolumeCreateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Delete will delete the existing Volume with the provided ID.
|
||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteURL(client, id), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Get retrieves the Volume with the provided ID. To extract the Volume object
|
||||
// from the response, call the Extract method on the GetResult.
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// ListOptsBuilder allows extensions to add additional parameters to the List
|
||||
// request.
|
||||
type ListOptsBuilder interface {
|
||||
ToVolumeListQuery() (string, error)
|
||||
}
|
||||
|
||||
// ListOpts holds options for listing Volumes. It is passed to the volumes.List
|
||||
// function.
|
||||
type ListOpts struct {
|
||||
// AllTenants will retrieve volumes of all tenants/projects.
|
||||
AllTenants bool `q:"all_tenants"`
|
||||
|
||||
// Metadata will filter results based on specified metadata.
|
||||
Metadata map[string]string `q:"metadata"`
|
||||
|
||||
// Name will filter by the specified volume name.
|
||||
Name string `q:"name"`
|
||||
|
||||
// Status will filter by the specified status.
|
||||
Status string `q:"status"`
|
||||
|
||||
// TenantID will filter by a specific tenant/project ID.
|
||||
// Setting AllTenants is required for this.
|
||||
TenantID string `q:"project_id"`
|
||||
|
||||
// Comma-separated list of sort keys and optional sort directions in the
|
||||
// form of <key>[:<direction>].
|
||||
Sort string `q:"sort"`
|
||||
|
||||
// Requests a page size of items.
|
||||
Limit int `q:"limit"`
|
||||
|
||||
// Used in conjunction with limit to return a slice of items.
|
||||
Offset int `q:"offset"`
|
||||
|
||||
// The ID of the last-seen item.
|
||||
Marker string `q:"marker"`
|
||||
}
|
||||
|
||||
// ToVolumeListQuery formats a ListOpts into a query string.
|
||||
func (opts ListOpts) ToVolumeListQuery() (string, error) {
|
||||
q, err := gophercloud.BuildQueryString(opts)
|
||||
return q.String(), err
|
||||
}
|
||||
|
||||
// List returns Volumes optionally limited by the conditions provided in ListOpts.
|
||||
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
||||
url := listURL(client)
|
||||
if opts != nil {
|
||||
query, err := opts.ToVolumeListQuery()
|
||||
if err != nil {
|
||||
return pagination.Pager{Err: err}
|
||||
}
|
||||
url += query
|
||||
}
|
||||
|
||||
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||
return VolumePage{pagination.LinkedPageBase{PageResult: r}}
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateOptsBuilder allows extensions to add additional parameters to the
|
||||
// Update request.
|
||||
type UpdateOptsBuilder interface {
|
||||
ToVolumeUpdateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// UpdateOpts contain options for updating an existing Volume. This object is passed
|
||||
// to the volumes.Update function. For more information about the parameters, see
|
||||
// the Volume object.
|
||||
type UpdateOpts struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// ToVolumeUpdateMap assembles a request body based on the contents of an
|
||||
// UpdateOpts.
|
||||
func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "volume")
|
||||
}
|
||||
|
||||
// Update will update the Volume with provided information. To extract the updated
|
||||
// Volume from the response, call the Extract method on the UpdateResult.
|
||||
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
|
||||
b, err := opts.ToVolumeUpdateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// IDFromName is a convienience function that returns a server's ID given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
count := 0
|
||||
id := ""
|
||||
pages, err := List(client, nil).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := ExtractVolumes(pages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, s := range all {
|
||||
if s.Name == name {
|
||||
count++
|
||||
id = s.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"}
|
||||
case 1:
|
||||
return id, nil
|
||||
default:
|
||||
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"}
|
||||
}
|
||||
}
|
170
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go
generated
vendored
Normal file
170
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go
generated
vendored
Normal file
|
@ -0,0 +1,170 @@
|
|||
package volumes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Attachment represents a Volume Attachment record
|
||||
type Attachment struct {
|
||||
AttachedAt time.Time `json:"-"`
|
||||
AttachmentID string `json:"attachment_id"`
|
||||
Device string `json:"device"`
|
||||
HostName string `json:"host_name"`
|
||||
ID string `json:"id"`
|
||||
ServerID string `json:"server_id"`
|
||||
VolumeID string `json:"volume_id"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON is our unmarshalling helper
|
||||
func (r *Attachment) UnmarshalJSON(b []byte) error {
|
||||
type tmp Attachment
|
||||
var s struct {
|
||||
tmp
|
||||
AttachedAt gophercloud.JSONRFC3339MilliNoZ `json:"attached_at"`
|
||||
}
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*r = Attachment(s.tmp)
|
||||
|
||||
r.AttachedAt = time.Time(s.AttachedAt)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Volume contains all the information associated with an OpenStack Volume.
|
||||
type Volume struct {
|
||||
// Unique identifier for the volume.
|
||||
ID string `json:"id"`
|
||||
// Current status of the volume.
|
||||
Status string `json:"status"`
|
||||
// Size of the volume in GB.
|
||||
Size int `json:"size"`
|
||||
// AvailabilityZone is which availability zone the volume is in.
|
||||
AvailabilityZone string `json:"availability_zone"`
|
||||
// The date when this volume was created.
|
||||
CreatedAt time.Time `json:"-"`
|
||||
// The date when this volume was last updated
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
// Instances onto which the volume is attached.
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
// Human-readable display name for the volume.
|
||||
Name string `json:"name"`
|
||||
// Human-readable description for the volume.
|
||||
Description string `json:"description"`
|
||||
// The type of volume to create, either SATA or SSD.
|
||||
VolumeType string `json:"volume_type"`
|
||||
// The ID of the snapshot from which the volume was created
|
||||
SnapshotID string `json:"snapshot_id"`
|
||||
// The ID of another block storage volume from which the current volume was created
|
||||
SourceVolID string `json:"source_volid"`
|
||||
// Arbitrary key-value pairs defined by the user.
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
// UserID is the id of the user who created the volume.
|
||||
UserID string `json:"user_id"`
|
||||
// Indicates whether this is a bootable volume.
|
||||
Bootable string `json:"bootable"`
|
||||
// Encrypted denotes if the volume is encrypted.
|
||||
Encrypted bool `json:"encrypted"`
|
||||
// ReplicationStatus is the status of replication.
|
||||
ReplicationStatus string `json:"replication_status"`
|
||||
// ConsistencyGroupID is the consistency group ID.
|
||||
ConsistencyGroupID string `json:"consistencygroup_id"`
|
||||
// Multiattach denotes if the volume is multi-attach capable.
|
||||
Multiattach bool `json:"multiattach"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON another unmarshalling function
|
||||
func (r *Volume) UnmarshalJSON(b []byte) error {
|
||||
type tmp Volume
|
||||
var s struct {
|
||||
tmp
|
||||
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
|
||||
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
|
||||
}
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*r = Volume(s.tmp)
|
||||
|
||||
r.CreatedAt = time.Time(s.CreatedAt)
|
||||
r.UpdatedAt = time.Time(s.UpdatedAt)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// VolumePage is a pagination.pager that is returned from a call to the List function.
|
||||
type VolumePage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty returns true if a ListResult contains no Volumes.
|
||||
func (r VolumePage) IsEmpty() (bool, error) {
|
||||
volumes, err := ExtractVolumes(r)
|
||||
return len(volumes) == 0, err
|
||||
}
|
||||
|
||||
func (page VolumePage) NextPageURL() (string, error) {
|
||||
var s struct {
|
||||
Links []gophercloud.Link `json:"volumes_links"`
|
||||
}
|
||||
err := page.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gophercloud.ExtractNextURL(s.Links)
|
||||
}
|
||||
|
||||
// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call.
|
||||
func ExtractVolumes(r pagination.Page) ([]Volume, error) {
|
||||
var s []Volume
|
||||
err := ExtractVolumesInto(r, &s)
|
||||
return s, err
|
||||
}
|
||||
|
||||
type commonResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract will get the Volume object out of the commonResult object.
|
||||
func (r commonResult) Extract() (*Volume, error) {
|
||||
var s Volume
|
||||
err := r.ExtractInto(&s)
|
||||
return &s, err
|
||||
}
|
||||
|
||||
// ExtractInto converts our response data into a volume struct
|
||||
func (r commonResult) ExtractInto(v interface{}) error {
|
||||
return r.Result.ExtractIntoStructPtr(v, "volume")
|
||||
}
|
||||
|
||||
// ExtractVolumesInto similar to ExtractInto but operates on a `list` of volumes
|
||||
func ExtractVolumesInto(r pagination.Page, v interface{}) error {
|
||||
return r.(VolumePage).Result.ExtractIntoSlicePtr(v, "volumes")
|
||||
}
|
||||
|
||||
// CreateResult contains the response body and error from a Create request.
|
||||
type CreateResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// GetResult contains the response body and error from a Get request.
|
||||
type GetResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// UpdateResult contains the response body and error from an Update request.
|
||||
type UpdateResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// DeleteResult contains the response body and error from a Delete request.
|
||||
type DeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
23
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go
generated
vendored
Normal file
23
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
package volumes
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func createURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("volumes")
|
||||
}
|
||||
|
||||
func listURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("volumes", "detail")
|
||||
}
|
||||
|
||||
func deleteURL(c *gophercloud.ServiceClient, id string) string {
|
||||
return c.ServiceURL("volumes", id)
|
||||
}
|
||||
|
||||
func getURL(c *gophercloud.ServiceClient, id string) string {
|
||||
return deleteURL(c, id)
|
||||
}
|
||||
|
||||
func updateURL(c *gophercloud.ServiceClient, id string) string {
|
||||
return deleteURL(c, id)
|
||||
}
|
22
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go
generated
vendored
Normal file
22
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
package volumes
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// WaitForStatus will continually poll the resource, checking for a particular
|
||||
// status. It will do this for the amount of seconds defined.
|
||||
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
|
||||
return gophercloud.WaitFor(secs, func() (bool, error) {
|
||||
current, err := Get(c, id).Extract()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if current.Status == status {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
})
|
||||
}
|
152
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/doc.go
generated
vendored
Normal file
152
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
Package bootfromvolume extends a server create request with the ability to
|
||||
specify block device options. This can be used to boot a server from a block
|
||||
storage volume as well as specify multiple ephemeral disks upon creation.
|
||||
|
||||
It is recommended to refer to the Block Device Mapping documentation to see
|
||||
all possible ways to configure a server's block devices at creation time:
|
||||
|
||||
https://docs.openstack.org/nova/latest/user/block-device-mapping.html
|
||||
|
||||
Note that this package implements `block_device_mapping_v2`.
|
||||
|
||||
Example of Creating a Server From an Image
|
||||
|
||||
This example will boot a server from an image and use a standard ephemeral
|
||||
disk as the server's root disk. This is virtually no different than creating
|
||||
a server without using block device mappings.
|
||||
|
||||
blockDevices := []bootfromvolume.BlockDevice{
|
||||
bootfromvolume.BlockDevice{
|
||||
BootIndex: 0,
|
||||
DeleteOnTermination: true,
|
||||
DestinationType: bootfromvolume.DestinationLocal,
|
||||
SourceType: bootfromvolume.SourceImage,
|
||||
UUID: "image-uuid",
|
||||
},
|
||||
}
|
||||
|
||||
serverCreateOpts := servers.CreateOpts{
|
||||
Name: "server_name",
|
||||
FlavorRef: "flavor-uuid",
|
||||
ImageRef: "image-uuid",
|
||||
}
|
||||
|
||||
createOpts := bootfromvolume.CreateOptsExt{
|
||||
CreateOptsBuilder: serverCreateOpts,
|
||||
BlockDevice: blockDevices,
|
||||
}
|
||||
|
||||
server, err := bootfromvolume.Create(client, createOpts).Extract()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Example of Creating a Server From a New Volume
|
||||
|
||||
This example will create a block storage volume based on the given Image. The
|
||||
server will use this volume as its root disk.
|
||||
|
||||
blockDevices := []bootfromvolume.BlockDevice{
|
||||
bootfromvolume.BlockDevice{
|
||||
DeleteOnTermination: true,
|
||||
DestinationType: bootfromvolume.DestinationVolume,
|
||||
SourceType: bootfromvolume.SourceImage,
|
||||
UUID: "image-uuid",
|
||||
VolumeSize: 2,
|
||||
},
|
||||
}
|
||||
|
||||
serverCreateOpts := servers.CreateOpts{
|
||||
Name: "server_name",
|
||||
FlavorRef: "flavor-uuid",
|
||||
}
|
||||
|
||||
createOpts := bootfromvolume.CreateOptsExt{
|
||||
CreateOptsBuilder: serverCreateOpts,
|
||||
BlockDevice: blockDevices,
|
||||
}
|
||||
|
||||
server, err := bootfromvolume.Create(client, createOpts).Extract()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Example of Creating a Server From an Existing Volume
|
||||
|
||||
This example will create a server with an existing volume as its root disk.
|
||||
|
||||
blockDevices := []bootfromvolume.BlockDevice{
|
||||
bootfromvolume.BlockDevice{
|
||||
DeleteOnTermination: true,
|
||||
DestinationType: bootfromvolume.DestinationVolume,
|
||||
SourceType: bootfromvolume.SourceVolume,
|
||||
UUID: "volume-uuid",
|
||||
},
|
||||
}
|
||||
|
||||
serverCreateOpts := servers.CreateOpts{
|
||||
Name: "server_name",
|
||||
FlavorRef: "flavor-uuid",
|
||||
}
|
||||
|
||||
createOpts := bootfromvolume.CreateOptsExt{
|
||||
CreateOptsBuilder: serverCreateOpts,
|
||||
BlockDevice: blockDevices,
|
||||
}
|
||||
|
||||
server, err := bootfromvolume.Create(client, createOpts).Extract()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Example of Creating a Server with Multiple Ephemeral Disks
|
||||
|
||||
This example will create a server with multiple ephemeral disks. The first
|
||||
block device will be based off of an existing Image. Each additional
|
||||
ephemeral disks must have an index of -1.
|
||||
|
||||
blockDevices := []bootfromvolume.BlockDevice{
|
||||
bootfromvolume.BlockDevice{
|
||||
BootIndex: 0,
|
||||
DestinationType: bootfromvolume.DestinationLocal,
|
||||
DeleteOnTermination: true,
|
||||
SourceType: bootfromvolume.SourceImage,
|
||||
UUID: "image-uuid",
|
||||
VolumeSize: 5,
|
||||
},
|
||||
bootfromvolume.BlockDevice{
|
||||
BootIndex: -1,
|
||||
DestinationType: bootfromvolume.DestinationLocal,
|
||||
DeleteOnTermination: true,
|
||||
GuestFormat: "ext4",
|
||||
SourceType: bootfromvolume.SourceBlank,
|
||||
VolumeSize: 1,
|
||||
},
|
||||
bootfromvolume.BlockDevice{
|
||||
BootIndex: -1,
|
||||
DestinationType: bootfromvolume.DestinationLocal,
|
||||
DeleteOnTermination: true,
|
||||
GuestFormat: "ext4",
|
||||
SourceType: bootfromvolume.SourceBlank,
|
||||
VolumeSize: 1,
|
||||
},
|
||||
}
|
||||
|
||||
serverCreateOpts := servers.CreateOpts{
|
||||
Name: "server_name",
|
||||
FlavorRef: "flavor-uuid",
|
||||
ImageRef: "image-uuid",
|
||||
}
|
||||
|
||||
createOpts := bootfromvolume.CreateOptsExt{
|
||||
CreateOptsBuilder: serverCreateOpts,
|
||||
BlockDevice: blockDevices,
|
||||
}
|
||||
|
||||
server, err := bootfromvolume.Create(client, createOpts).Extract()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
*/
|
||||
package bootfromvolume
|
120
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go
generated
vendored
Normal file
120
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
package bootfromvolume
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
)
|
||||
|
||||
type (
|
||||
// DestinationType represents the type of medium being used as the
|
||||
// destination of the bootable device.
|
||||
DestinationType string
|
||||
|
||||
// SourceType represents the type of medium being used as the source of the
|
||||
// bootable device.
|
||||
SourceType string
|
||||
)
|
||||
|
||||
const (
|
||||
// DestinationLocal DestinationType is for using an ephemeral disk as the
|
||||
// destination.
|
||||
DestinationLocal DestinationType = "local"
|
||||
|
||||
// DestinationVolume DestinationType is for using a volume as the destination.
|
||||
DestinationVolume DestinationType = "volume"
|
||||
|
||||
// SourceBlank SourceType is for a "blank" or empty source.
|
||||
SourceBlank SourceType = "blank"
|
||||
|
||||
// SourceImage SourceType is for using images as the source of a block device.
|
||||
SourceImage SourceType = "image"
|
||||
|
||||
// SourceSnapshot SourceType is for using a volume snapshot as the source of
|
||||
// a block device.
|
||||
SourceSnapshot SourceType = "snapshot"
|
||||
|
||||
// SourceVolume SourceType is for using a volume as the source of block
|
||||
// device.
|
||||
SourceVolume SourceType = "volume"
|
||||
)
|
||||
|
||||
// BlockDevice is a structure with options for creating block devices in a
|
||||
// server. The block device may be created from an image, snapshot, new volume,
|
||||
// or existing volume. The destination may be a new volume, existing volume
|
||||
// which will be attached to the instance, ephemeral disk, or boot device.
|
||||
type BlockDevice struct {
|
||||
// SourceType must be one of: "volume", "snapshot", "image", or "blank".
|
||||
SourceType SourceType `json:"source_type" required:"true"`
|
||||
|
||||
// UUID is the unique identifier for the existing volume, snapshot, or
|
||||
// image (see above).
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
|
||||
// BootIndex is the boot index. It defaults to 0.
|
||||
BootIndex int `json:"boot_index"`
|
||||
|
||||
// DeleteOnTermination specifies whether or not to delete the attached volume
|
||||
// when the server is deleted. Defaults to `false`.
|
||||
DeleteOnTermination bool `json:"delete_on_termination"`
|
||||
|
||||
// DestinationType is the type that gets created. Possible values are "volume"
|
||||
// and "local".
|
||||
DestinationType DestinationType `json:"destination_type,omitempty"`
|
||||
|
||||
// GuestFormat specifies the format of the block device.
|
||||
GuestFormat string `json:"guest_format,omitempty"`
|
||||
|
||||
// VolumeSize is the size of the volume to create (in gigabytes). This can be
|
||||
// omitted for existing volumes.
|
||||
VolumeSize int `json:"volume_size,omitempty"`
|
||||
}
|
||||
|
||||
// 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 {
|
||||
err := gophercloud.ErrMissingInput{}
|
||||
err.Argument = "bootfromvolume.CreateOptsExt.BlockDevice"
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverMap := base["server"].(map[string]interface{})
|
||||
|
||||
blockDevice := make([]map[string]interface{}, len(opts.BlockDevice))
|
||||
|
||||
for i, bd := range opts.BlockDevice {
|
||||
b, err := gophercloud.BuildRequestBody(bd, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockDevice[i] = b
|
||||
}
|
||||
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) (r servers.CreateResult) {
|
||||
b, err := opts.ToServerCreateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 202},
|
||||
})
|
||||
return
|
||||
}
|
12
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/results.go
generated
vendored
Normal file
12
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/results.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
package bootfromvolume
|
||||
|
||||
import (
|
||||
os "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
)
|
||||
|
||||
// CreateResult temporarily contains the response from a Create call.
|
||||
// It embeds the standard servers.CreateResults type and so can be used the
|
||||
// same way as a standard server request result.
|
||||
type CreateResult struct {
|
||||
os.CreateResult
|
||||
}
|
7
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/urls.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package bootfromvolume
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func createURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("os-volumes_boot")
|
||||
}
|
|
@ -737,6 +737,36 @@
|
|||
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
|
||||
"revisionTime": "2018-05-31T02:06:30Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "8YtBD+Um7I8ee1Xf1ZAWu74eP7w=",
|
||||
"path": "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions",
|
||||
"revision": "282f25e4025de0a42015d2e2b5faef1d920aad3c",
|
||||
"revisionTime": "2018-05-15T01:47:05Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "vqXNCd2My0y/5tPC/Bs79uf6Q4E=",
|
||||
"path": "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes",
|
||||
"revision": "282f25e4025de0a42015d2e2b5faef1d920aad3c",
|
||||
"revisionTime": "2018-05-15T01:47:05Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Au6MAsI90lewLByg9n+Yjtdqdh8=",
|
||||
"path": "github.com/gophercloud/gophercloud/openstack/common/extensions",
|
||||
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
|
||||
"revisionTime": "2017-06-23T01:44:30Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "4XWDCGMYqipwJymi9xJo9UffD7g=",
|
||||
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions",
|
||||
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
|
||||
"revisionTime": "2017-06-23T01:44:30Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "kCHEEeRVZeR1LhbvNP+WyvB8z2s=",
|
||||
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume",
|
||||
"revision": "282f25e4025de0a42015d2e2b5faef1d920aad3c",
|
||||
"revisionTime": "2018-05-15T01:47:05Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "vFS5BwnCdQIfKm1nNWrR+ijsAZA=",
|
||||
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips",
|
||||
|
|
|
@ -36,6 +36,9 @@ installed *if you are using temporary key pairs*, i.e. don't use
|
|||
OS'es have OpenSSL installed by default except Windows. This have been
|
||||
resolved in OpenStack Ocata(Feb 2017).
|
||||
|
||||
~> **Note:** OpenStack Block Storage volume support is available only for
|
||||
V3 Block Storage API. It's available in OpenStack since Mitaka release
|
||||
(Apr 2016).
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
|
@ -206,6 +209,22 @@ builder.
|
|||
- `user_data_file` (string) - Path to a file that will be used for the user
|
||||
data when launching the instance.
|
||||
|
||||
- `use_blockstorage_volume` (boolean) - Use Block Storage service volume for
|
||||
the instance root volume instead of Compute service local volume (default).
|
||||
|
||||
- `volume_name` (string) - Name of the Block Storage service volume. If this
|
||||
isn't specified, random string will be used.
|
||||
|
||||
- `volume_type` (string) - Type of the Block Storage service volume. If this
|
||||
isn't specified, the default enforced by your OpenStack cluster will be
|
||||
used.
|
||||
|
||||
- `volume_availability_zone` (string) - Availability zone of the Block
|
||||
Storage service volume. If omitted, Compute instance availability zone will
|
||||
be used. If both of Compute instance and Block Storage volume availability
|
||||
zones aren't specified, the default enforced by your OpenStack cluster will
|
||||
be used.
|
||||
|
||||
## Basic Example: DevStack
|
||||
|
||||
Here is a basic example. This is a example to build on DevStack running in a VM.
|
||||
|
@ -293,6 +312,31 @@ export OS_USER_DOMAIN_NAME="mydomain"
|
|||
export OS_PROJECT_DOMAIN_NAME="mydomain"
|
||||
```
|
||||
|
||||
## Basic Example: Instance with Block Storage root volume
|
||||
|
||||
A basic example of Instance with a remote root Block Storage service volume.
|
||||
This is a working example to build an image on private OpenStack cloud powered
|
||||
by Selectel VPC.
|
||||
|
||||
``` json
|
||||
{
|
||||
"type": "openstack",
|
||||
"identity_endpoint": "https://api.selvpc.com/identity/v3",
|
||||
"tenant_id": "2e90c5c04c7b4c509be78723e2b55b77",
|
||||
"username": "foo",
|
||||
"password": "foo",
|
||||
"region": "ru-3",
|
||||
"ssh_username": "root",
|
||||
"image_name": "Test image",
|
||||
"source_image": "5f58ea7e-6264-4939-9d0f-0c23072b1132",
|
||||
"networks": "9aab504e-bedf-48af-9256-682a7fa3dabb",
|
||||
"flavor": "1001",
|
||||
"availability_zone": "ru-3a",
|
||||
"use_blockstorage_volume": true,
|
||||
"volume_type": "fast.ru-3a"
|
||||
}
|
||||
```
|
||||
|
||||
## Notes on OpenStack Authorization
|
||||
|
||||
The simplest way to get all settings for authorization against OpenStack is to
|
||||
|
|
Loading…
Reference in New Issue