2019-06-13 03:16:49 -04:00
|
|
|
package uhost
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2019-12-17 05:25:56 -05:00
|
|
|
"math/rand"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2019-10-12 04:46:21 -04:00
|
|
|
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
|
2019-06-13 03:16:49 -04:00
|
|
|
"github.com/hashicorp/packer/common/retry"
|
|
|
|
"github.com/hashicorp/packer/helper/multistep"
|
|
|
|
"github.com/hashicorp/packer/packer"
|
|
|
|
"github.com/ucloud/ucloud-sdk-go/services/uhost"
|
|
|
|
"github.com/ucloud/ucloud-sdk-go/ucloud"
|
|
|
|
)
|
|
|
|
|
|
|
|
type stepCreateInstance struct {
|
|
|
|
Region string
|
|
|
|
Zone string
|
|
|
|
InstanceType string
|
|
|
|
InstanceName string
|
|
|
|
BootDiskType string
|
|
|
|
SourceImageId string
|
|
|
|
UsePrivateIp bool
|
|
|
|
|
|
|
|
instanceId string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
2019-10-12 04:46:21 -04:00
|
|
|
client := state.Get("client").(*ucloudcommon.UCloudClient)
|
|
|
|
conn := client.UHostConn
|
2019-06-13 03:16:49 -04:00
|
|
|
ui := state.Get("ui").(packer.Ui)
|
|
|
|
|
|
|
|
ui.Say("Creating Instance...")
|
2019-06-19 09:32:33 -04:00
|
|
|
resp, err := conn.CreateUHostInstance(s.buildCreateInstanceRequest(state))
|
2019-06-13 03:16:49 -04:00
|
|
|
if err != nil {
|
2019-10-12 04:46:21 -04:00
|
|
|
return ucloudcommon.Halt(state, err, "Error on creating instance")
|
2019-06-13 03:16:49 -04:00
|
|
|
}
|
|
|
|
instanceId := resp.UHostIds[0]
|
|
|
|
|
|
|
|
err = retry.Config{
|
|
|
|
Tries: 20,
|
|
|
|
ShouldRetry: func(err error) bool {
|
2019-10-12 04:46:21 -04:00
|
|
|
return ucloudcommon.IsExpectedStateError(err)
|
2019-06-13 03:16:49 -04:00
|
|
|
},
|
|
|
|
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 6 * time.Second, Multiplier: 2}).Linear,
|
|
|
|
}.Run(ctx, func(ctx context.Context) error {
|
2019-10-12 04:46:21 -04:00
|
|
|
inst, err := client.DescribeUHostById(instanceId)
|
2019-06-13 03:16:49 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-06-19 09:32:33 -04:00
|
|
|
|
|
|
|
if inst.State == "ResizeFail" {
|
|
|
|
return fmt.Errorf("resizing instance failed")
|
|
|
|
}
|
|
|
|
|
|
|
|
if inst.State == "Install Fail" {
|
|
|
|
return fmt.Errorf("install failed")
|
|
|
|
}
|
|
|
|
|
2019-10-12 04:46:21 -04:00
|
|
|
if inst == nil || inst.State != ucloudcommon.InstanceStateRunning {
|
|
|
|
return ucloudcommon.NewExpectedStateError("instance", instanceId)
|
2019-06-13 03:16:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2019-10-12 04:46:21 -04:00
|
|
|
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on waiting for instance %q to become available", instanceId))
|
2019-06-13 03:16:49 -04:00
|
|
|
}
|
|
|
|
|
2019-06-19 09:32:33 -04:00
|
|
|
ui.Message(fmt.Sprintf("Creating instance %q complete", instanceId))
|
2019-10-12 04:46:21 -04:00
|
|
|
instance, err := client.DescribeUHostById(instanceId)
|
2019-06-13 03:16:49 -04:00
|
|
|
if err != nil {
|
2019-10-12 04:46:21 -04:00
|
|
|
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on reading instance when creating %q", instanceId))
|
2019-06-13 03:16:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
s.instanceId = instanceId
|
|
|
|
state.Put("instance", instance)
|
2019-12-13 14:57:01 -05:00
|
|
|
// instance_id is the generic term used so that users can have access to the
|
|
|
|
// instance id inside of the provisioners, used in step_provision.
|
|
|
|
state.Put("instance_id", instance)
|
2019-06-13 03:16:49 -04:00
|
|
|
|
2019-10-12 04:46:21 -04:00
|
|
|
if instance.BootDiskState != ucloudcommon.BootDiskStateNormal {
|
2019-06-19 09:32:33 -04:00
|
|
|
ui.Say("Waiting for boot disk of instance initialized")
|
|
|
|
if s.BootDiskType == "local_normal" || s.BootDiskType == "local_ssd" {
|
|
|
|
ui.Message(fmt.Sprintf("Warning: It takes around 10 mins for boot disk initialization when `boot_disk_type` is %q", s.BootDiskType))
|
|
|
|
}
|
2019-06-13 03:16:49 -04:00
|
|
|
|
|
|
|
err = retry.Config{
|
|
|
|
Tries: 200,
|
|
|
|
ShouldRetry: func(err error) bool {
|
2019-10-12 04:46:21 -04:00
|
|
|
return ucloudcommon.IsExpectedStateError(err)
|
2019-06-13 03:16:49 -04:00
|
|
|
},
|
|
|
|
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 12 * time.Second, Multiplier: 2}).Linear,
|
|
|
|
}.Run(ctx, func(ctx context.Context) error {
|
2019-10-12 04:46:21 -04:00
|
|
|
inst, err := client.DescribeUHostById(instanceId)
|
2019-06-13 03:16:49 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-10-12 04:46:21 -04:00
|
|
|
if inst.BootDiskState != ucloudcommon.BootDiskStateNormal {
|
|
|
|
return ucloudcommon.NewExpectedStateError("boot_disk of instance", instanceId)
|
2019-06-13 03:16:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2019-10-12 04:46:21 -04:00
|
|
|
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on waiting for boot disk of instance %q initialized", instanceId))
|
2019-06-13 03:16:49 -04:00
|
|
|
}
|
|
|
|
|
2019-06-19 09:32:33 -04:00
|
|
|
ui.Message(fmt.Sprintf("Waiting for boot disk of instance %q initialized complete", instanceId))
|
2019-06-13 03:16:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return multistep.ActionContinue
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *stepCreateInstance) Cleanup(state multistep.StateBag) {
|
|
|
|
if s.instanceId == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_, cancelled := state.GetOk(multistep.StateCancelled)
|
|
|
|
_, halted := state.GetOk(multistep.StateHalted)
|
|
|
|
|
|
|
|
ui := state.Get("ui").(packer.Ui)
|
|
|
|
ctx := context.TODO()
|
|
|
|
|
|
|
|
if cancelled || halted {
|
|
|
|
ui.Say("Deleting instance because of cancellation or error...")
|
|
|
|
} else {
|
|
|
|
ui.Say("Deleting instance...")
|
|
|
|
}
|
|
|
|
|
2019-10-12 04:46:21 -04:00
|
|
|
client := state.Get("client").(*ucloudcommon.UCloudClient)
|
|
|
|
conn := client.UHostConn
|
2019-06-13 03:16:49 -04:00
|
|
|
|
2019-10-12 04:46:21 -04:00
|
|
|
instance, err := client.DescribeUHostById(s.instanceId)
|
2019-06-13 03:16:49 -04:00
|
|
|
if err != nil {
|
2019-10-12 04:46:21 -04:00
|
|
|
if ucloudcommon.IsNotFoundError(err) {
|
2019-06-13 03:16:49 -04:00
|
|
|
return
|
|
|
|
}
|
2019-06-19 09:32:33 -04:00
|
|
|
ui.Error(fmt.Sprintf("Error on reading instance when deleting %q, %s",
|
2019-06-13 03:16:49 -04:00
|
|
|
s.instanceId, err.Error()))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-10-12 04:46:21 -04:00
|
|
|
if instance.State != ucloudcommon.InstanceStateStopped {
|
2019-07-26 05:03:57 -04:00
|
|
|
stopReq := conn.NewStopUHostInstanceRequest()
|
2019-06-19 09:32:33 -04:00
|
|
|
stopReq.UHostId = ucloud.String(s.instanceId)
|
2019-07-26 05:03:57 -04:00
|
|
|
if _, err = conn.StopUHostInstance(stopReq); err != nil {
|
2019-06-19 09:32:33 -04:00
|
|
|
ui.Error(fmt.Sprintf("Error on stopping instance when deleting %q, %s",
|
2019-06-13 03:16:49 -04:00
|
|
|
s.instanceId, err.Error()))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = retry.Config{
|
|
|
|
Tries: 30,
|
|
|
|
ShouldRetry: func(err error) bool {
|
2019-10-12 04:46:21 -04:00
|
|
|
return ucloudcommon.IsExpectedStateError(err)
|
2019-06-13 03:16:49 -04:00
|
|
|
},
|
|
|
|
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 6 * time.Second, Multiplier: 2}).Linear,
|
|
|
|
}.Run(ctx, func(ctx context.Context) error {
|
2019-10-12 04:46:21 -04:00
|
|
|
instance, err := client.DescribeUHostById(s.instanceId)
|
2019-06-13 03:16:49 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-10-12 04:46:21 -04:00
|
|
|
if instance.State != ucloudcommon.InstanceStateStopped {
|
|
|
|
return ucloudcommon.NewExpectedStateError("instance", s.instanceId)
|
2019-06-13 03:16:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2019-06-19 09:32:33 -04:00
|
|
|
ui.Error(fmt.Sprintf("Error on waiting for stopping instance when deleting %q, %s",
|
2019-06-13 03:16:49 -04:00
|
|
|
s.instanceId, err.Error()))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
deleteReq := conn.NewTerminateUHostInstanceRequest()
|
|
|
|
deleteReq.UHostId = ucloud.String(s.instanceId)
|
|
|
|
deleteReq.ReleaseUDisk = ucloud.Bool(true)
|
|
|
|
deleteReq.ReleaseEIP = ucloud.Bool(true)
|
|
|
|
|
2019-06-19 09:32:33 -04:00
|
|
|
if _, err = conn.TerminateUHostInstance(deleteReq); err != nil {
|
2019-06-13 03:16:49 -04:00
|
|
|
ui.Error(fmt.Sprintf("Error on deleting instance %q, %s",
|
|
|
|
s.instanceId, err.Error()))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = retry.Config{
|
|
|
|
Tries: 30,
|
2019-10-12 04:46:21 -04:00
|
|
|
ShouldRetry: func(err error) bool { return !ucloudcommon.IsNotFoundError(err) },
|
2019-06-13 03:16:49 -04:00
|
|
|
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 6 * time.Second, Multiplier: 2}).Linear,
|
|
|
|
}.Run(ctx, func(ctx context.Context) error {
|
2019-10-12 04:46:21 -04:00
|
|
|
_, err := client.DescribeUHostById(s.instanceId)
|
2019-06-13 03:16:49 -04:00
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2019-06-28 00:05:23 -04:00
|
|
|
ui.Error(fmt.Sprintf("Error on waiting for instance %q to be deleted: %s",
|
2019-06-13 03:16:49 -04:00
|
|
|
s.instanceId, err.Error()))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-06-19 09:32:33 -04:00
|
|
|
ui.Message(fmt.Sprintf("Deleting instance %q complete", s.instanceId))
|
2019-06-13 03:16:49 -04:00
|
|
|
}
|
|
|
|
|
2019-06-19 09:32:33 -04:00
|
|
|
func (s *stepCreateInstance) buildCreateInstanceRequest(state multistep.StateBag) *uhost.CreateUHostInstanceRequest {
|
2019-10-12 04:46:21 -04:00
|
|
|
client := state.Get("client").(*ucloudcommon.UCloudClient)
|
|
|
|
conn := client.UHostConn
|
2019-06-13 03:16:49 -04:00
|
|
|
srcImage := state.Get("source_image").(*uhost.UHostImageSet)
|
|
|
|
config := state.Get("config").(*Config)
|
|
|
|
connectConfig := &config.RunConfig.Comm
|
|
|
|
|
|
|
|
var password string
|
|
|
|
if srcImage.OsType == "Linux" {
|
|
|
|
password = config.Comm.SSHPassword
|
|
|
|
}
|
|
|
|
|
|
|
|
if password == "" {
|
|
|
|
password = fmt.Sprintf("%s%s%s",
|
2019-10-12 04:46:21 -04:00
|
|
|
s.randStringFromCharSet(5, ucloudcommon.DefaultPasswordStr),
|
|
|
|
s.randStringFromCharSet(1, ucloudcommon.DefaultPasswordSpe),
|
|
|
|
s.randStringFromCharSet(5, ucloudcommon.DefaultPasswordNum))
|
2019-06-13 03:16:49 -04:00
|
|
|
if srcImage.OsType == "Linux" {
|
|
|
|
connectConfig.SSHPassword = password
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
req := conn.NewCreateUHostInstanceRequest()
|
2019-10-12 04:46:21 -04:00
|
|
|
t, _ := ucloudcommon.ParseInstanceType(s.InstanceType)
|
2019-06-13 03:16:49 -04:00
|
|
|
|
|
|
|
req.CPU = ucloud.Int(t.CPU)
|
|
|
|
req.Memory = ucloud.Int(t.Memory)
|
|
|
|
req.Name = ucloud.String(s.InstanceName)
|
|
|
|
req.LoginMode = ucloud.String("Password")
|
|
|
|
req.Zone = ucloud.String(s.Zone)
|
|
|
|
req.ImageId = ucloud.String(s.SourceImageId)
|
|
|
|
req.ChargeType = ucloud.String("Dynamic")
|
|
|
|
req.Password = ucloud.String(password)
|
|
|
|
|
2019-10-12 04:46:21 -04:00
|
|
|
req.MachineType = ucloud.String("N")
|
|
|
|
req.MinimalCpuPlatform = ucloud.String("Intel/Auto")
|
|
|
|
if t.HostType == "o" {
|
|
|
|
req.MachineType = ucloud.String("O")
|
|
|
|
}
|
|
|
|
|
2019-06-13 03:16:49 -04:00
|
|
|
if v, ok := state.GetOk("security_group_id"); ok {
|
|
|
|
req.SecurityGroupId = ucloud.String(v.(string))
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := state.GetOk("vpc_id"); ok {
|
|
|
|
req.VPCId = ucloud.String(v.(string))
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := state.GetOk("subnet_id"); ok {
|
|
|
|
req.SubnetId = ucloud.String(v.(string))
|
|
|
|
}
|
|
|
|
|
|
|
|
bootDisk := uhost.UHostDisk{}
|
|
|
|
bootDisk.IsBoot = ucloud.String("true")
|
|
|
|
bootDisk.Size = ucloud.Int(srcImage.ImageSize)
|
2019-10-12 04:46:21 -04:00
|
|
|
bootDisk.Type = ucloud.String(ucloudcommon.BootDiskTypeMap.Convert(s.BootDiskType))
|
2019-06-13 03:16:49 -04:00
|
|
|
|
|
|
|
req.Disks = append(req.Disks, bootDisk)
|
|
|
|
|
|
|
|
if !s.UsePrivateIp {
|
|
|
|
operatorName := ucloud.String("International")
|
|
|
|
if strings.HasPrefix(s.Region, "cn-") {
|
2019-10-24 05:10:48 -04:00
|
|
|
operatorName = ucloud.String("Bgp")
|
2019-06-13 03:16:49 -04:00
|
|
|
}
|
|
|
|
networkInterface := uhost.CreateUHostInstanceParamNetworkInterface{
|
|
|
|
EIP: &uhost.CreateUHostInstanceParamNetworkInterfaceEIP{
|
|
|
|
Bandwidth: ucloud.Int(30),
|
|
|
|
PayMode: ucloud.String("Traffic"),
|
|
|
|
OperatorName: operatorName,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
req.NetworkInterface = append(req.NetworkInterface, networkInterface)
|
|
|
|
}
|
2019-06-19 09:32:33 -04:00
|
|
|
return req
|
2019-06-13 03:16:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *stepCreateInstance) randStringFromCharSet(strlen int, charSet string) string {
|
|
|
|
rand.Seed(time.Now().UTC().UnixNano())
|
|
|
|
result := make([]byte, strlen)
|
|
|
|
for i := 0; i < strlen; i++ {
|
|
|
|
result[i] = charSet[rand.Intn(len(charSet))]
|
|
|
|
}
|
|
|
|
return string(result)
|
|
|
|
}
|