From b246bf732922347f2d5f532d593b50433a536890 Mon Sep 17 00:00:00 2001 From: Marin Salinas Date: Sat, 23 Feb 2019 20:49:51 -0600 Subject: [PATCH] wip: add create_volume step in chroot builder --- builder/osc/chroot/builder.go | 6 + builder/osc/chroot/step_create_volume.go | 170 +++++++++++++++++++++++ builder/osc/common/state.go | 37 +++++ builder/osc/common/temp_const.go | 18 +++ 4 files changed, 231 insertions(+) create mode 100644 builder/osc/chroot/step_create_volume.go create mode 100644 builder/osc/common/temp_const.go diff --git a/builder/osc/chroot/builder.go b/builder/osc/chroot/builder.go index 7c64edbc5..9a3d1d54f 100644 --- a/builder/osc/chroot/builder.go +++ b/builder/osc/chroot/builder.go @@ -241,6 +241,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe steps = append(steps, &StepFlock{}, &StepPrepareDevice{}, + &StepCreateVolume{ + RootVolumeType: b.config.RootVolumeType, + RootVolumeSize: b.config.RootVolumeSize, + RootVolumeTags: b.config.RootVolumeTags, + Ctx: b.config.ctx, + }, ) // Run! diff --git a/builder/osc/chroot/step_create_volume.go b/builder/osc/chroot/step_create_volume.go new file mode 100644 index 000000000..3d7434f42 --- /dev/null +++ b/builder/osc/chroot/step_create_volume.go @@ -0,0 +1,170 @@ +package chroot + +import ( + "context" + "errors" + "fmt" + "log" + + osccommon "github.com/hashicorp/packer/builder/osc/common" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" + "github.com/hashicorp/packer/template/interpolate" + "github.com/outscale/osc-go/oapi" +) + +// StepCreateVolume creates a new volume from the snapshot of the root +// device of the OMI. +// +// Produces: +// volume_id string - The ID of the created volume +type StepCreateVolume struct { + volumeId string + RootVolumeSize int64 + RootVolumeType string + RootVolumeTags osccommon.TagMap + Ctx interpolate.Context +} + +func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + oapiconn := state.Get("oapi").(*oapi.Client) + vm := state.Get("vm").(oapi.Vm) + ui := state.Get("ui").(packer.Ui) + + var err error + + //TODO: Add tags + // volTags, err := s.RootVolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state) + // if err != nil { + // err := fmt.Errorf("Error tagging volumes: %s", err) + // state.Put("error", err) + // ui.Error(err.Error()) + // return multistep.ActionHalt + // } + + // // Collect tags for tagging on resource creation + // var tagSpecs []oapi.ResourceTag + + // if len(volTags) > 0 { + // runVolTags := &oapi.Resou rceTag{ + // ResourceType: "volume", + // Tags: volTags, + // } + + // tagSpecs = append(tagSpecs, runVolTags) + // } + + var createVolume *oapi.CreateVolumeRequest + if config.FromScratch { + rootVolumeType := osccommon.VolumeTypeGp2 + if s.RootVolumeType == "io1" { + err := errors.New("Cannot use io1 volume when building from scratch") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } else if s.RootVolumeType != "" { + rootVolumeType = s.RootVolumeType + } + createVolume = &oapi.CreateVolumeRequest{ + SubregionName: vm.Placement.SubregionName, + Size: s.RootVolumeSize, + VolumeType: rootVolumeType, + } + + } else { + // Determine the root device snapshot + image := state.Get("source_image").(oapi.Image) + log.Printf("Searching for root device of the image (%s)", image.RootDeviceName) + var rootDevice *oapi.BlockDeviceMappingImage + for _, device := range image.BlockDeviceMappings { + if device.DeviceName == image.RootDeviceName { + *rootDevice = device + break + } + } + + ui.Say("Creating the root volume...") + createVolume, err = s.buildCreateVolumeInput(vm.Placement.SubregionName, rootDevice) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + //TODO: ADD TAGS + // if len(tagSpecs) > 0 { + // createVolume.SetTagSpecifications(tagSpecs) + // volTags.Report(ui) + // } + log.Printf("Create args: %+v", createVolume) + + createVolumeResp, err := oapiconn.POST_CreateVolume(*createVolume) + if err != nil { + err := fmt.Errorf("Error creating root volume: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Set the volume ID so we remember to delete it later + s.volumeId = createVolumeResp.OK.Volume.VolumeId + log.Printf("Volume ID: %s", s.volumeId) + + // Wait for the volume to become ready + err = osccommon.WaitUntilVolumeAvailable(oapiconn, s.volumeId) + if err != nil { + err := fmt.Errorf("Error waiting for volume: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + state.Put("volume_id", s.volumeId) + return multistep.ActionContinue +} + +func (s *StepCreateVolume) Cleanup(state multistep.StateBag) { + if s.volumeId == "" { + return + } + + oapiconn := state.Get("oapi").(*oapi.Client) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deleting the created BSU volume...") + _, err := oapiconn.POST_DeleteVolume(oapi.DeleteVolumeRequest{VolumeId: s.volumeId}) + if err != nil { + ui.Error(fmt.Sprintf("Error deleting BSU volume: %s", err)) + } +} + +func (s *StepCreateVolume) buildCreateVolumeInput(suregionName string, rootDevice *oapi.BlockDeviceMappingImage) (*oapi.CreateVolumeRequest, error) { + if rootDevice == nil { + return nil, fmt.Errorf("Couldn't find root device!") + } + createVolumeInput := &oapi.CreateVolumeRequest{ + SubregionName: suregionName, + Size: rootDevice.Bsu.VolumeSize, + SnapshotId: rootDevice.Bsu.SnapshotId, + VolumeType: rootDevice.Bsu.VolumeType, + Iops: rootDevice.Bsu.Iops, + } + if s.RootVolumeSize > rootDevice.Bsu.VolumeSize { + createVolumeInput.Size = s.RootVolumeSize + } + + if s.RootVolumeType == "" || s.RootVolumeType == rootDevice.Bsu.VolumeType { + return createVolumeInput, nil + } + + if s.RootVolumeType == "io1" { + return nil, fmt.Errorf("Root volume type cannot be io1, because existing root volume type was %s", rootDevice.Bsu.VolumeType) + } + + createVolumeInput.VolumeType = s.RootVolumeType + // non io1 cannot set iops + createVolumeInput.Iops = 0 + + return createVolumeInput, nil +} diff --git a/builder/osc/common/state.go b/builder/osc/common/state.go index ed8941268..d0fd7a2db 100644 --- a/builder/osc/common/state.go +++ b/builder/osc/common/state.go @@ -48,6 +48,12 @@ func WaitUntilImageAvailable(conn *oapi.Client, imageID string) error { return <-errCh } +func WaitUntilVolumeAvailable(conn *oapi.Client, volumeID string) error { + errCh := make(chan error, 1) + go waitForState(errCh, "available", volumeWaitFunc(conn, volumeID)) + return <-errCh +} + func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error { err := common.Retry(2, 2, 0, func(_ uint) (bool, error) { state, err := refresh() @@ -173,3 +179,34 @@ func securityGroupWaitFunc(conn *oapi.Client, id string) stateRefreshFunc { return "exists", nil } } + +func volumeWaitFunc(conn *oapi.Client, id string) stateRefreshFunc { + return func() (string, error) { + log.Printf("[Debug] Check if SvolumeG with id %s exists", id) + resp, err := conn.POST_ReadVolumes(oapi.ReadVolumesRequest{ + Filters: oapi.FiltersVolume{ + VolumeIds: []string{id}, + }, + }) + + log.Printf("[Debug] Read Response %+v", resp.OK) + + if err != nil { + return "", err + } + + if resp.OK == nil { + return "", fmt.Errorf("Volume with ID %s. Not Found", id) + } + + if len(resp.OK.Volumes) == 0 { + return "waiting", nil + } + + if resp.OK.Volumes[0].State == "error" { + return resp.OK.Volumes[0].State, fmt.Errorf("Volume (%s) creation is failed", id) + } + + return resp.OK.Volumes[0].State, nil + } +} diff --git a/builder/osc/common/temp_const.go b/builder/osc/common/temp_const.go new file mode 100644 index 000000000..d43e0276d --- /dev/null +++ b/builder/osc/common/temp_const.go @@ -0,0 +1,18 @@ +package common + +const ( + // VolumeTypeStandard is a VolumeType enum value + VolumeTypeStandard = "standard" + + // VolumeTypeIo1 is a VolumeType enum value + VolumeTypeIo1 = "io1" + + // VolumeTypeGp2 is a VolumeType enum value + VolumeTypeGp2 = "gp2" + + // VolumeTypeSc1 is a VolumeType enum value + VolumeTypeSc1 = "sc1" + + // VolumeTypeSt1 is a VolumeType enum value + VolumeTypeSt1 = "st1" +)