OpenStack builder: add StepDetachVolume
Add a step of detaching Compute instance volume if it's created using the Block Storage service. It is needed to create a final image. This commit also moves common volume functions to a different file and fixes some messages in the StepCreateVolume.
This commit is contained in:
parent
005f7e56a7
commit
c0ffe7eb89
|
@ -132,6 +132,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
},
|
},
|
||||||
&common.StepProvision{},
|
&common.StepProvision{},
|
||||||
&StepStopServer{},
|
&StepStopServer{},
|
||||||
|
&StepDetachVolume{
|
||||||
|
UseBlockStorageVolume: b.config.UseBlockStorageVolume,
|
||||||
|
},
|
||||||
&stepCreateImage{},
|
&stepCreateImage{},
|
||||||
&stepUpdateImageVisibility{},
|
&stepUpdateImageVisibility{},
|
||||||
&stepAddImageMembers{},
|
&stepAddImageMembers{},
|
||||||
|
|
|
@ -3,12 +3,8 @@ package openstack
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gophercloud/gophercloud"
|
|
||||||
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
|
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
|
||||||
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
)
|
)
|
||||||
|
@ -63,7 +59,6 @@ func (s *StepCreateVolume) Run(_ context.Context, state multistep.StateBag) mult
|
||||||
Name: s.VolumeName,
|
Name: s.VolumeName,
|
||||||
ImageID: s.SourceImage,
|
ImageID: s.SourceImage,
|
||||||
}
|
}
|
||||||
|
|
||||||
volume, err := volumes.Create(blockStorageClient, volumeOpts).Extract()
|
volume, err := volumes.Create(blockStorageClient, volumeOpts).Extract()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error creating volume: %s", err)
|
err := fmt.Errorf("Error creating volume: %s", err)
|
||||||
|
@ -72,8 +67,8 @@ func (s *StepCreateVolume) Run(_ context.Context, state multistep.StateBag) mult
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the volume to become ready.
|
// Wait for volume to become available.
|
||||||
ui.Say(fmt.Sprintf("Waiting for volume %s (volume id: %s) to become ready...", config.VolumeName, volume.ID))
|
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 {
|
if err := WaitForVolume(blockStorageClient, volume.ID); err != nil {
|
||||||
err := fmt.Errorf("Error waiting for volume: %s", err)
|
err := fmt.Errorf("Error waiting for volume: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
@ -92,63 +87,6 @@ func (s *StepCreateVolume) Run(_ context.Context, state multistep.StateBag) mult
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForVolume waits for the given volume to become ready.
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
|
func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
|
||||||
if !s.doCleanup {
|
if !s.doCleanup {
|
||||||
return
|
return
|
||||||
|
|
|
@ -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.
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue