Skip to content

Commit 651b3ef

Browse files
committed
enhancement: cvm - support instance reset and pay as you go
1 parent c4a110b commit 651b3ef

File tree

5 files changed

+204
-95
lines changed

5 files changed

+204
-95
lines changed

tencentcloud/extension_cvm.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ const (
5151

5252
CVM_ZONE_NOT_SUPPORT_ERROR = "InvalidParameterValue.ZoneNotSupported"
5353
CVM_CLOUD_DISK_SOLD_OUT_ERROR = "ResourceInsufficient.CloudDiskSoldOut"
54+
55+
CVM_STOP_MODE_KEEP_CHARGING = "KEEP_CHARGING"
56+
CVM_STOP_MODE_STOP_CHARGING = "STOP_CHARGING"
5457
)
5558

5659
var CVM_CHARGE_TYPE = []string{

tencentcloud/resource_tc_instance.go

Lines changed: 167 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ resource "tencentcloud_cdh_instance" "foo" {
7878
host_type = "HM50"
7979
charge_type = "PREPAID"
8080
instance_charge_type_prepaid_period = 1
81-
host_name = "test"
81+
hostname = "test"
8282
prepaid_renew_flag = "DISABLE_NOTIFY_AND_MANUAL_RENEW"
8383
}
8484
8585
data "tencentcloud_cdh_instances" "list" {
8686
availability_zone = var.availability_zone
8787
host_id = tencentcloud_cdh_instance.foo.id
88-
host_name = "test"
88+
hostname = "test"
8989
host_state = "RUNNING"
9090
}
9191
@@ -167,8 +167,7 @@ func resourceTencentCloudInstance() *schema.Resource {
167167
"image_id": {
168168
Type: schema.TypeString,
169169
Required: true,
170-
ForceNew: true,
171-
Description: "The image to use for the instance. Changing `image_id` will cause the instance to be destroyed and re-created.",
170+
Description: "The image to use for the instance. Changing `image_id` will cause the instance reset.",
172171
},
173172
"availability_zone": {
174173
Type: schema.TypeString,
@@ -180,6 +179,7 @@ func resourceTencentCloudInstance() *schema.Resource {
180179
Type: schema.TypeInt,
181180
Optional: true,
182181
Default: 1,
182+
Deprecated: "It has been deprecated from version 1.59.17. Use built-in `count` instead.",
183183
ValidateFunc: validateIntegerInRange(1, 100),
184184
Description: "The number of instances to be purchased. Value range:[1,100]; default value: 1.",
185185
},
@@ -200,8 +200,7 @@ func resourceTencentCloudInstance() *schema.Resource {
200200
"hostname": {
201201
Type: schema.TypeString,
202202
Optional: true,
203-
ForceNew: true,
204-
Description: "The hostname of the instance. Windows instance: The name should be a combination of 2 to 15 characters comprised of letters (case insensitive), numbers, and hyphens (-). Period (.) is not supported, and the name cannot be a string of pure numbers. Other types (such as Linux) of instances: The name should be a combination of 2 to 60 characters, supporting multiple periods (.). The piece between two periods is composed of letters (case insensitive), numbers, and hyphens (-).",
203+
Description: "The hostname of the instance. Windows instance: The name should be a combination of 2 to 15 characters comprised of letters (case insensitive), numbers, and hyphens (-). Period (.) is not supported, and the name cannot be a string of pure numbers. Other types (such as Linux) of instances: The name should be a combination of 2 to 60 characters, supporting multiple periods (.). The piece between two periods is composed of letters (case insensitive), numbers, and hyphens (-). Modifying will cause the instance reset.",
205204
},
206205
"project_id": {
207206
Type: schema.TypeInt,
@@ -215,6 +214,15 @@ func resourceTencentCloudInstance() *schema.Resource {
215214
Default: true,
216215
Description: "Set instance to running or stop. Default value is true, the instance will shutdown when this flag is false.",
217216
},
217+
"stopped_mode": {
218+
Type: schema.TypeString,
219+
Optional: true,
220+
Description: "Billing method of a pay-as-you-go instance after shutdown. Available values: `KEEP_CHARGING`,`STOP_CHARGING`. Default `KEEP_CHARGING`.",
221+
ValidateFunc: validateAllowedStringValue([]string{
222+
CVM_STOP_MODE_KEEP_CHARGING,
223+
CVM_STOP_MODE_STOP_CHARGING,
224+
}),
225+
},
218226
"placement_group_id": {
219227
Type: schema.TypeString,
220228
Optional: true,
@@ -234,14 +242,14 @@ func resourceTencentCloudInstance() *schema.Resource {
234242
Type: schema.TypeInt,
235243
Optional: true,
236244
ValidateFunc: validateAllowedIntValue(CVM_PREPAID_PERIOD),
237-
Description: "The tenancy (time unit is month) of the prepaid instance, NOTE: it only works when instance_charge_type is set to `PREPAID`. Valid values are `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `10`, `11`, `12`, `24`, `36`.",
245+
Description: "The tenancy (time unit is month) of the prepaid instance, NOTE: it only works when instance_charge_type is set to `PREPAID`. Valid values are `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `10`, `11`, `12`, `24`, `36`. Modifying will cause the instance reset.",
238246
},
239247
"instance_charge_type_prepaid_renew_flag": {
240248
Type: schema.TypeString,
241249
Optional: true,
242250
Computed: true,
243251
ValidateFunc: validateAllowedStringValue(CVM_PREPAID_RENEW_FLAG),
244-
Description: "Auto renewal flag. Valid values: `NOTIFY_AND_AUTO_RENEW`: notify upon expiration and renew automatically, `NOTIFY_AND_MANUAL_RENEW`: notify upon expiration but do not renew automatically, `DISABLE_NOTIFY_AND_MANUAL_RENEW`: neither notify upon expiration nor renew automatically. Default value: `NOTIFY_AND_MANUAL_RENEW`. If this parameter is specified as `NOTIFY_AND_AUTO_RENEW`, the instance will be automatically renewed on a monthly basis if the account balance is sufficient. NOTE: it only works when instance_charge_type is set to `PREPAID`.",
252+
Description: "Auto renewal flag. Valid values: `NOTIFY_AND_AUTO_RENEW`: notify upon expiration and renew automatically, `NOTIFY_AND_MANUAL_RENEW`: notify upon expiration but do not renew automatically, `DISABLE_NOTIFY_AND_MANUAL_RENEW`: neither notify upon expiration nor renew automatically. Default value: `NOTIFY_AND_MANUAL_RENEW`. If this parameter is specified as `NOTIFY_AND_AUTO_RENEW`, the instance will be automatically renewed on a monthly basis if the account balance is sufficient. NOTE: it only works when instance_charge_type is set to `PREPAID`. Modifying will cause the instance reset.",
245253
},
246254
"spot_instance_type": {
247255
Type: schema.TypeString,
@@ -439,9 +447,8 @@ func resourceTencentCloudInstance() *schema.Resource {
439447
return old == new
440448
}
441449
},
442-
ForceNew: true,
443450
ConflictsWith: []string{"key_name", "password"},
444-
Description: "Whether to keep image login or not, default is `false`. When the image type is private or shared or imported, this parameter can be set `true`.",
451+
Description: "Whether to keep image login or not, default is `false`. When the image type is private or shared or imported, this parameter can be set `true`. Modifying will cause the instance reset.",
445452
},
446453
"user_data": {
447454
Type: schema.TypeString,
@@ -797,7 +804,8 @@ func resourceTencentCloudInstanceCreate(d *schema.ResourceData, meta interface{}
797804
}
798805

799806
if !(d.Get("running_flag").(bool)) {
800-
err = cvmService.StopInstance(ctx, instanceId)
807+
stoppedMode := d.Get("stopped_mode").(string)
808+
err = cvmService.StopInstance(ctx, instanceId, stoppedMode)
801809
if err != nil {
802810
return err
803811
}
@@ -1038,6 +1046,152 @@ func resourceTencentCloudInstanceUpdate(d *schema.ResourceData, meta interface{}
10381046
d.SetPartial("project_id")
10391047
}
10401048

1049+
// Reset Instance
1050+
// Keep Login Info
1051+
if d.HasChange("image_id") ||
1052+
d.HasChange("host_name") ||
1053+
d.HasChange("disable_security_service") ||
1054+
d.HasChange("disable_monitor_service") ||
1055+
d.HasChange("keep_image_login") {
1056+
1057+
var updateAttr []string
1058+
1059+
request := cvm.NewResetInstanceRequest()
1060+
request.InstanceId = helper.String(d.Id())
1061+
1062+
if v, ok := d.GetOk("image_id"); ok {
1063+
updateAttr = append(updateAttr, "image_id")
1064+
request.ImageId = helper.String(v.(string))
1065+
}
1066+
if v, ok := d.GetOk("hostname"); ok {
1067+
updateAttr = append(updateAttr, "hostname")
1068+
request.HostName = helper.String(v.(string))
1069+
}
1070+
1071+
// enhanced service
1072+
request.EnhancedService = &cvm.EnhancedService{}
1073+
if d.HasChange("disable_security_service") {
1074+
updateAttr = append(updateAttr, "disable_security_service")
1075+
v := d.Get("disable_security_service")
1076+
securityService := v.(bool)
1077+
request.EnhancedService.SecurityService = &cvm.RunSecurityServiceEnabled{
1078+
Enabled: &securityService,
1079+
}
1080+
}
1081+
1082+
if d.HasChange("disable_monitor_service") {
1083+
updateAttr = append(updateAttr, "disable_monitor_service")
1084+
v := d.Get("disable_monitor_service")
1085+
monitorService := !(v.(bool))
1086+
request.EnhancedService.MonitorService = &cvm.RunMonitorServiceEnabled{
1087+
Enabled: &monitorService,
1088+
}
1089+
}
1090+
1091+
// Modify or keep login info when instance reset
1092+
request.LoginSettings = &cvm.LoginSettings{}
1093+
1094+
if v, ok := d.GetOk("password"); ok {
1095+
updateAttr = append(updateAttr, "password")
1096+
request.LoginSettings.Password = helper.String(v.(string))
1097+
}
1098+
1099+
if v, ok := d.GetOk("key_name"); ok {
1100+
updateAttr = append(updateAttr, "key_name")
1101+
request.LoginSettings.KeyIds = []*string{helper.String(v.(string))}
1102+
}
1103+
1104+
if d.HasChange("keep_image_login") {
1105+
updateAttr = append(updateAttr, "keep_image_login")
1106+
}
1107+
1108+
if v := d.Get("keep_image_login").(bool); v {
1109+
request.LoginSettings.KeepImageLogin = helper.String(CVM_IMAGE_LOGIN)
1110+
} else {
1111+
request.LoginSettings.KeepImageLogin = helper.String(CVM_IMAGE_LOGIN_NOT)
1112+
}
1113+
1114+
if err := cvmService.ResetInstance(ctx, request); err != nil {
1115+
return err
1116+
}
1117+
1118+
for _, attr := range updateAttr {
1119+
d.SetPartial(attr)
1120+
}
1121+
1122+
// Modify Login Info Directly
1123+
} else {
1124+
if d.HasChange("password") {
1125+
err := cvmService.ModifyPassword(ctx, instanceId, d.Get("password").(string))
1126+
if err != nil {
1127+
return err
1128+
}
1129+
d.SetPartial("password")
1130+
time.Sleep(10 * time.Second)
1131+
err = resource.Retry(2*readRetryTimeout, func() *resource.RetryError {
1132+
instance, errRet := cvmService.DescribeInstanceById(ctx, instanceId)
1133+
if errRet != nil {
1134+
return retryError(errRet, InternalError)
1135+
}
1136+
if instance != nil && *instance.LatestOperationState == CVM_LATEST_OPERATION_STATE_OPERATING {
1137+
return resource.RetryableError(fmt.Errorf("cvm instance latest operetion status is %s, retry...", *instance.LatestOperationState))
1138+
}
1139+
return nil
1140+
})
1141+
if err != nil {
1142+
return err
1143+
}
1144+
}
1145+
1146+
if d.HasChange("key_name") {
1147+
old, new := d.GetChange("key_name")
1148+
oldKeyId := old.(string)
1149+
keyId := new.(string)
1150+
if oldKeyId != "" {
1151+
err := cvmService.UnbindKeyPair(ctx, oldKeyId, []*string{&instanceId})
1152+
if err != nil {
1153+
return err
1154+
}
1155+
time.Sleep(10 * time.Second)
1156+
err = resource.Retry(2*readRetryTimeout, func() *resource.RetryError {
1157+
instance, errRet := cvmService.DescribeInstanceById(ctx, instanceId)
1158+
if errRet != nil {
1159+
return retryError(errRet, InternalError)
1160+
}
1161+
if instance != nil && *instance.LatestOperationState == CVM_LATEST_OPERATION_STATE_OPERATING {
1162+
return resource.RetryableError(fmt.Errorf("cvm instance latest operetion status is %s, retry...", *instance.LatestOperationState))
1163+
}
1164+
return nil
1165+
})
1166+
if err != nil {
1167+
return err
1168+
}
1169+
}
1170+
1171+
if keyId != "" {
1172+
err = cvmService.BindKeyPair(ctx, keyId, instanceId)
1173+
if err != nil {
1174+
return err
1175+
}
1176+
time.Sleep(10 * time.Second)
1177+
err = resource.Retry(2*readRetryTimeout, func() *resource.RetryError {
1178+
instance, errRet := cvmService.DescribeInstanceById(ctx, instanceId)
1179+
if errRet != nil {
1180+
return retryError(errRet, InternalError)
1181+
}
1182+
if instance != nil && *instance.LatestOperationState == CVM_LATEST_OPERATION_STATE_OPERATING {
1183+
return resource.RetryableError(fmt.Errorf("cvm instance latest operetion status is %s, retry...", *instance.LatestOperationState))
1184+
}
1185+
return nil
1186+
})
1187+
if err != nil {
1188+
return err
1189+
}
1190+
}
1191+
d.SetPartial("key_name")
1192+
}
1193+
}
1194+
10411195
var flag bool
10421196
if d.HasChange("running_flag") {
10431197
flag = d.Get("running_flag").(bool)
@@ -1060,7 +1214,8 @@ func resourceTencentCloudInstanceUpdate(d *schema.ResourceData, meta interface{}
10601214
return err
10611215
}
10621216
} else {
1063-
err = cvmService.StopInstance(ctx, instanceId)
1217+
stoppedMode := d.Get("stopped_mode").(string)
1218+
err = cvmService.StopInstance(ctx, instanceId, stoppedMode)
10641219
if err != nil {
10651220
return err
10661221
}
@@ -1133,76 +1288,6 @@ func resourceTencentCloudInstanceUpdate(d *schema.ResourceData, meta interface{}
11331288
}
11341289
}
11351290

1136-
if d.HasChange("password") {
1137-
err := cvmService.ModifyPassword(ctx, instanceId, d.Get("password").(string))
1138-
if err != nil {
1139-
return err
1140-
}
1141-
d.SetPartial("password")
1142-
time.Sleep(10 * time.Second)
1143-
err = resource.Retry(2*readRetryTimeout, func() *resource.RetryError {
1144-
instance, errRet := cvmService.DescribeInstanceById(ctx, instanceId)
1145-
if errRet != nil {
1146-
return retryError(errRet, InternalError)
1147-
}
1148-
if instance != nil && *instance.LatestOperationState == CVM_LATEST_OPERATION_STATE_OPERATING {
1149-
return resource.RetryableError(fmt.Errorf("cvm instance latest operetion status is %s, retry...", *instance.LatestOperationState))
1150-
}
1151-
return nil
1152-
})
1153-
if err != nil {
1154-
return err
1155-
}
1156-
}
1157-
1158-
if d.HasChange("key_name") {
1159-
old, new := d.GetChange("key_name")
1160-
oldKeyId := old.(string)
1161-
keyId := new.(string)
1162-
if oldKeyId != "" {
1163-
err := cvmService.UnbindKeyPair(ctx, oldKeyId, []*string{&instanceId})
1164-
if err != nil {
1165-
return err
1166-
}
1167-
time.Sleep(10 * time.Second)
1168-
err = resource.Retry(2*readRetryTimeout, func() *resource.RetryError {
1169-
instance, errRet := cvmService.DescribeInstanceById(ctx, instanceId)
1170-
if errRet != nil {
1171-
return retryError(errRet, InternalError)
1172-
}
1173-
if instance != nil && *instance.LatestOperationState == CVM_LATEST_OPERATION_STATE_OPERATING {
1174-
return resource.RetryableError(fmt.Errorf("cvm instance latest operetion status is %s, retry...", *instance.LatestOperationState))
1175-
}
1176-
return nil
1177-
})
1178-
if err != nil {
1179-
return err
1180-
}
1181-
}
1182-
1183-
if keyId != "" {
1184-
err = cvmService.BindKeyPair(ctx, keyId, instanceId)
1185-
if err != nil {
1186-
return err
1187-
}
1188-
time.Sleep(10 * time.Second)
1189-
err = resource.Retry(2*readRetryTimeout, func() *resource.RetryError {
1190-
instance, errRet := cvmService.DescribeInstanceById(ctx, instanceId)
1191-
if errRet != nil {
1192-
return retryError(errRet, InternalError)
1193-
}
1194-
if instance != nil && *instance.LatestOperationState == CVM_LATEST_OPERATION_STATE_OPERATING {
1195-
return resource.RetryableError(fmt.Errorf("cvm instance latest operetion status is %s, retry...", *instance.LatestOperationState))
1196-
}
1197-
return nil
1198-
})
1199-
if err != nil {
1200-
return err
1201-
}
1202-
}
1203-
d.SetPartial("key_name")
1204-
}
1205-
12061291
if d.HasChange("vpc_id") || d.HasChange("subnet_id") || d.HasChange("private_ip") {
12071292
vpcId := d.Get("vpc_id").(string)
12081293
subnetId := d.Get("subnet_id").(string)

tencentcloud/service_tencentcloud_cvm.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,13 @@ func (me *CvmService) ModifyVpc(ctx context.Context, instanceId, vpcId, subnetId
225225
return nil
226226
}
227227

228-
func (me *CvmService) StopInstance(ctx context.Context, instanceId string) error {
228+
func (me *CvmService) StopInstance(ctx context.Context, instanceId string, stoppedMode string) error {
229229
logId := getLogId(ctx)
230230
request := cvm.NewStopInstancesRequest()
231231
request.InstanceIds = []*string{&instanceId}
232+
if stoppedMode != "" {
233+
request.StoppedMode = &stoppedMode
234+
}
232235

233236
ratelimit.Check(request.GetAction())
234237
response, err := me.client.UseCvmClient().StopInstances(request)
@@ -279,6 +282,23 @@ func (me *CvmService) DeleteInstance(ctx context.Context, instanceId string) err
279282
return nil
280283
}
281284

285+
func (me *CvmService) ResetInstance(ctx context.Context, request *cvm.ResetInstanceRequest) (errRet error) {
286+
logId := getLogId(ctx)
287+
ratelimit.Check(request.GetAction())
288+
289+
response, err := me.client.UseCvmClient().ResetInstance(request)
290+
291+
if err != nil {
292+
log.Printf("[CRITAL]%s api[%s] fail, request body [%s], reason[%s]\n",
293+
logId, request.GetAction(), request.ToJsonString(), err.Error())
294+
return err
295+
}
296+
log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n",
297+
logId, request.GetAction(), request.ToJsonString(), response.ToJsonString())
298+
299+
return nil
300+
}
301+
282302
func (me *CvmService) DescribeInstanceTypes(ctx context.Context, zone string) (instanceTypes []*cvm.InstanceTypeConfig, errRet error) {
283303
logId := getLogId(ctx)
284304
request := cvm.NewDescribeInstanceTypeConfigsRequest()

0 commit comments

Comments
 (0)