diff --git a/builder/openstack/builder.go b/builder/openstack/builder.go index 26105a9fd..5bffead8e 100644 --- a/builder/openstack/builder.go +++ b/builder/openstack/builder.go @@ -132,6 +132,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &common.StepProvision{}, &StepStopServer{}, + &StepDetachVolume{ + UseBlockStorageVolume: b.config.UseBlockStorageVolume, + }, &stepCreateImage{}, &stepUpdateImageVisibility{}, &stepAddImageMembers{}, diff --git a/builder/openstack/step_create_volume.go b/builder/openstack/step_create_volume.go index 6ad569c0b..a188012f9 100644 --- a/builder/openstack/step_create_volume.go +++ b/builder/openstack/step_create_volume.go @@ -3,12 +3,8 @@ package openstack import ( "context" "fmt" - "log" - "time" - "github.com/gophercloud/gophercloud" "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/packer" ) @@ -63,7 +59,6 @@ func (s *StepCreateVolume) Run(_ context.Context, state multistep.StateBag) mult Name: s.VolumeName, ImageID: s.SourceImage, } - volume, err := volumes.Create(blockStorageClient, volumeOpts).Extract() if err != nil { 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 } - // Wait for the volume to become ready. - ui.Say(fmt.Sprintf("Waiting for volume %s (volume id: %s) to become ready...", config.VolumeName, volume.ID)) + // 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) @@ -92,63 +87,6 @@ func (s *StepCreateVolume) Run(_ context.Context, state multistep.StateBag) mult 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) { if !s.doCleanup { return diff --git a/builder/openstack/step_detach_volume.go b/builder/openstack/step_detach_volume.go new file mode 100644 index 000000000..ff739e94d --- /dev/null +++ b/builder/openstack/step_detach_volume.go @@ -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. +} diff --git a/builder/openstack/volume.go b/builder/openstack/volume.go new file mode 100644 index 000000000..5eba12578 --- /dev/null +++ b/builder/openstack/volume.go @@ -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 +}