Skip to content

Commit

Permalink
add ucloud_instance_state resource (#152)
Browse files Browse the repository at this point in the history
* add ucloud_instance_state resource

* support stop_instance_before_detaching in ucloud instance
  • Loading branch information
wangrzneu committed Aug 27, 2023
1 parent 29d19d0 commit ae0b990
Show file tree
Hide file tree
Showing 10 changed files with 452 additions and 21 deletions.
9 changes: 9 additions & 0 deletions ucloud/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ const (
k8sNodeStatusStopped = "Stopped"
)

const (
instanceStatusInitializing = "Initializing"
instanceStatusStarting = "Starting"
instanceStatusRunning = "Running"
instanceStatusStopping = "Stopping"
instanceStatusStopped = "Stopped"
instanceStatusRebooting = "Rebooting"
)

const (
resourceTypeInstance = "instance"
resourceTypeLb = "lb"
Expand Down
2 changes: 1 addition & 1 deletion ucloud/data_source_ucloud_iam_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func dataSourceUCloudIAMPolicyRead(d *schema.ResourceData, meta interface{}) err
case "Custom":
owner = "User"
default:
errors.New("type not supported")
return errors.New("type not supported")
}
policy, err := client.describeIAMPolicyByName(d.Get("name").(string), owner)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions ucloud/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ func Provider() terraform.ResourceProvider {
"ucloud_iam_policy": resourceUCloudIAMPolicy(),
"ucloud_iam_user_policy_attachment": resourceUCloudIAMUserPolicyAttachment(),
"ucloud_iam_group_policy_attachment": resourceUCloudIAMGroupPolicyAttachment(),
"ucloud_instance_state": resourceUCloudInstanceState(),
},
ConfigureFunc: providerConfigure,
}
Expand Down
20 changes: 16 additions & 4 deletions ucloud/resource_ucloud_disk_attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import (

func resourceUCloudDiskAttachment() *schema.Resource {
return &schema.Resource{
Create: resourceUCloudDiskAttachmentCreate,
Read: resourceUCloudDiskAttachmentRead,
Delete: resourceUCloudDiskAttachmentDelete,

Create: resourceUCloudDiskAttachmentCreate,
Read: resourceUCloudDiskAttachmentRead,
Delete: resourceUCloudDiskAttachmentDelete,
Update: schema.Noop,
SchemaVersion: 1,
MigrateState: resourceUCloudDiskAttachmentMigrateState,

Expand All @@ -38,6 +38,11 @@ func resourceUCloudDiskAttachment() *schema.Resource {
Required: true,
ForceNew: true,
},

"stop_instance_before_detaching": {
Type: schema.TypeBool,
Optional: true,
},
},
}
}
Expand Down Expand Up @@ -109,6 +114,13 @@ func resourceUCloudDiskAttachmentDelete(d *schema.ResourceData, meta interface{}
req.UDiskId = ucloud.String(p[0])
req.UHostId = ucloud.String(p[1])

if _, ok := d.GetOk("stop_instance_before_detaching"); ok {
err := WaitAndUpdateInstanceState(client, *req.UHostId, instanceStatusStopped, false, d.Timeout(schema.TimeoutDelete))
if err != nil {
return fmt.Errorf("error on stop instance %q before deleting, %s", *req.UHostId, err)
}
}

return resource.Retry(15*time.Minute, func() *resource.RetryError {
_, err := client.describeDiskResource(p[0], p[1])
if err != nil {
Expand Down
31 changes: 19 additions & 12 deletions ucloud/resource_ucloud_disk_attachment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,65 +108,72 @@ func testAccCheckDiskAttachmentDestroy(s *terraform.State) error {
}

const testAccDiskAttachmentConfig = `
data "ucloud_zones" "default" {}
variable "availability_zone" {
type = string
default = "cn-bj2-05"
}
data "ucloud_images" "default" {
availability_zone = "${data.ucloud_zones.default.zones.0.id}"
availability_zone = "cn-bj2-05"
name_regex = "^CentOS 7.[1-2] 64"
image_type = "base"
}
resource "ucloud_disk" "foo" {
availability_zone = "${data.ucloud_zones.default.zones.0.id}"
availability_zone = "${var.availability_zone}"
name = "tf-acc-disk-attachment"
disk_size = 20
}
resource "ucloud_instance" "foo" {
name = "tf-acc-disk-attachment"
instance_type = "n-highcpu-1"
availability_zone = "${data.ucloud_zones.default.zones.0.id}"
availability_zone = "${var.availability_zone}"
image_id = "${data.ucloud_images.default.images.0.id}"
charge_type = "month"
duration = 1
root_password = "wA123456"
}
resource "ucloud_disk_attachment" "foo" {
availability_zone = "${data.ucloud_zones.default.zones.0.id}"
availability_zone = "${var.availability_zone}"
disk_id = "${ucloud_disk.foo.id}"
instance_id = "${ucloud_instance.foo.id}"
}
`

const testAccDiskAttachmentConfigUpdate = `
data "ucloud_zones" "default" {}
variable "availability_zone" {
type = string
default = "cn-bj2-05"
}
data "ucloud_images" "default" {
availability_zone = "${data.ucloud_zones.default.zones.0.id}"
availability_zone = "${var.availability_zone}"
name_regex = "^CentOS 7.[1-2] 64"
image_type = "base"
}
resource "ucloud_disk" "foo" {
availability_zone = "${data.ucloud_zones.default.zones.0.id}"
availability_zone = "${var.availability_zone}"
name = "tf-acc-disk-attachment"
disk_size = 40
}
resource "ucloud_instance" "foo" {
name = "tf-acc-disk-attachment"
instance_type = "n-highcpu-1"
availability_zone = "${data.ucloud_zones.default.zones.0.id}"
availability_zone = "${var.availability_zone}"
image_id = "${data.ucloud_images.default.images.0.id}"
charge_type = "month"
duration = 1
root_password = "wA123456"
}
resource "ucloud_disk_attachment" "foo" {
availability_zone = "${data.ucloud_zones.default.zones.0.id}"
disk_id = "${ucloud_disk.foo.id}"
instance_id = "${ucloud_instance.foo.id}"
availability_zone = "${var.availability_zone}"
disk_id = "${ucloud_disk.foo.id}"
instance_id = "${ucloud_instance.foo.id}"
stop_instance_before_detaching = true
}
`
161 changes: 161 additions & 0 deletions ucloud/resource_ucloud_instance_state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package ucloud

import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/ucloud/ucloud-sdk-go/services/uhost"
"time"
)

func resourceUCloudInstanceState() *schema.Resource {
return &schema.Resource{
Create: resourceUCloudInstanceStateCreate,
Read: resourceUCloudInstanceStateRead,
Update: resourceUCloudInstanceStateUpdate,
Delete: resourceUCloudInstanceStateDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"instance_id": {
Type: schema.TypeString,
Required: true,
},
"state": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
"Stopped",
"Running",
}, false),
},
"force": {
Type: schema.TypeBool,
Optional: true,
},
},
}
}

func resourceUCloudInstanceStateCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*UCloudClient)
instanceId := d.Get("instance_id").(string)
state := d.Get("state").(string)
force := d.Get("force").(bool)

err := WaitAndUpdateInstanceState(client, instanceId, state, force, d.Timeout(schema.TimeoutCreate))
if err != nil {
return err
}
d.SetId(instanceId)
return resourceUCloudInstanceStateRead(d, meta)
}

func resourceUCloudInstanceStateRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*UCloudClient)
instanceId := d.Id()

state, err := client.getInstanceState(instanceId)
if err != nil {
return err
}
d.Set("instance_id", instanceId)
d.Set("state", state)
d.Set("force", d.Get("force").(bool))
return nil
}

func resourceUCloudInstanceStateUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*UCloudClient)
instanceId := d.Id()
state := d.Get("state").(string)
force := d.Get("force").(bool)

err := WaitAndUpdateInstanceState(client, instanceId, state, force, d.Timeout(schema.TimeoutUpdate))
if err != nil {
return err
}
return resourceUCloudInstanceStateRead(d, meta)
}

func resourceUCloudInstanceStateDelete(d *schema.ResourceData, meta interface{}) error {
return nil
}

func waitInstanceReady(client *UCloudClient, id string, timeout time.Duration) (*uhost.UHostInstanceSet, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{statusPending, instanceStatusInitializing, instanceStatusStarting, instanceStatusStopping, instanceStatusRebooting},
Target: []string{instanceStatusRunning, instanceStatusStopped},
Refresh: getInstanceStateRefreshFunc(client, id),
Timeout: timeout,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}

outputRaw, err := stateConf.WaitForState()

if output, ok := outputRaw.(*uhost.UHostInstanceSet); ok {
return output, err
}

return nil, err
}

func getInstanceStateRefreshFunc(client *UCloudClient, instanceId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
instance, err := client.describeInstanceById(instanceId)
if err != nil {
if isNotFoundError(err) {
return nil, statusPending, nil
}
return nil, "", err
}

state := instance.State
if state == instanceStatusResizeFail {
return nil, "", fmt.Errorf("resizing instance failed")
}

if state == instanceStatusInstallFail {
return nil, "", fmt.Errorf("install failed")
}

return instance, state, nil
}
}

func updateInstanceState(client *UCloudClient, instance uhost.UHostInstanceSet, state string, force bool) error {
switch instance.State {
case instanceStatusStopped:
if state == instanceStatusRunning {
return client.startInstanceById(instance.UHostId)
}
case instanceStatusRunning:
if state == instanceStatusStopped {
if force {
return client.poweroffInstanceById(instance.UHostId)
} else {
return client.stopInstanceById(instance.UHostId)
}
}
}
return nil
}

func WaitAndUpdateInstanceState(client *UCloudClient, instanceId string, state string, force bool, timeout time.Duration) error {
instance, instanceErr := waitInstanceReady(client, instanceId, timeout)
if instanceErr != nil {
return fmt.Errorf("error on waiting instance reach a ready status %v", instanceErr)
}
err := updateInstanceState(client, *instance, state, force)
if err != nil {
return err
}
_, instanceErr = waitInstanceReady(client, instanceId, timeout)
if instanceErr != nil {
return fmt.Errorf("error on waiting instance reach a ready status %v", instanceErr)
}
return nil
}
Loading

0 comments on commit ae0b990

Please sign in to comment.