diff --git a/.changelog/2461.txt b/.changelog/2461.txt new file mode 100644 index 0000000000..e131a06563 --- /dev/null +++ b/.changelog/2461.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/tencentcloud_emr_cluster: support import +``` \ No newline at end of file diff --git a/tencentcloud/services/emr/resource_tc_emr_cluster.go b/tencentcloud/services/emr/resource_tc_emr_cluster.go index c9fc6adf72..69f10de348 100644 --- a/tencentcloud/services/emr/resource_tc_emr_cluster.go +++ b/tencentcloud/services/emr/resource_tc_emr_cluster.go @@ -6,6 +6,7 @@ import ( "fmt" tccommon "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/common" + "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/internal/helper" svccdb "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/services/cdb" svctag "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/services/tag" @@ -22,11 +23,14 @@ func ResourceTencentCloudEmrCluster() *schema.Resource { Read: resourceTencentCloudEmrClusterRead, Delete: resourceTencentCloudEmrClusterDelete, Update: resourceTencentCloudEmrClusterUpdate, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Schema: map[string]*schema.Schema{ "display_strategy": { Type: schema.TypeString, - Required: true, - ForceNew: true, + Optional: true, + Deprecated: "It will be deprecated in later versions.", Description: "Display strategy of EMR instance.", }, "product_id": { @@ -54,7 +58,7 @@ func ResourceTencentCloudEmrCluster() *schema.Resource { Description: "The private net config of EMR instance.", }, "softwares": { - Type: schema.TypeList, + Type: schema.TypeSet, Required: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, @@ -71,22 +75,26 @@ func ResourceTencentCloudEmrCluster() *schema.Resource { "task_resource_spec": buildResourceSpecSchema(), "master_count": { Type: schema.TypeInt, + Computed: true, Optional: true, Description: "The number of master node.", }, "core_count": { Type: schema.TypeInt, + Computed: true, Optional: true, Description: "The number of core node.", }, "task_count": { Type: schema.TypeInt, + Computed: true, Optional: true, Description: "The number of core node.", }, "common_resource_spec": buildResourceSpecSchema(), "common_count": { Type: schema.TypeInt, + Computed: true, Optional: true, ForceNew: true, Description: "The number of common node.", @@ -116,25 +124,50 @@ func ResourceTencentCloudEmrCluster() *schema.Resource { Description: "The pay mode of instance. 0 represent POSTPAID_BY_HOUR, 1 represent PREPAID.", }, "placement": { - Type: schema.TypeMap, - Required: true, - ForceNew: true, + Type: schema.TypeMap, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"placement", "placement_info"}, + Deprecated: "It will be deprecated in later versions. Use `placement_info` instead.", + Description: "The location of the instance.", + }, + "placement_info": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + ExactlyOneOf: []string{"placement", "placement_info"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "zone": { + Type: schema.TypeString, + Required: true, + Description: "Zone.", + }, + "project_id": { + Type: schema.TypeInt, + Computed: true, + Optional: true, + Description: "Project id.", + }, + }, + }, Description: "The location of the instance.", }, "time_span": { Type: schema.TypeInt, - Required: true, + Optional: true, Description: "The length of time the instance was purchased. Use with TimeUnit.When TimeUnit is s, the parameter can only be filled in at 3600, representing a metered instance.\nWhen TimeUnit is m, the number filled in by this parameter indicates the length of purchase of the monthly instance of the package year, such as 1 for one month of purchase.", }, "time_unit": { Type: schema.TypeString, - Required: true, + Optional: true, Description: "The unit of time in which the instance was purchased. When PayMode is 0, TimeUnit can only take values of s(second). When PayMode is 1, TimeUnit can only take the value m(month).", }, "login_settings": { Type: schema.TypeMap, - Required: true, - ForceNew: true, + Optional: true, + Sensitive: true, Description: "Instance login settings.", }, "extend_fs_field": { @@ -178,6 +211,14 @@ func resourceTencentCloudEmrClusterUpdate(d *schema.ResourceData, meta interface defer tccommon.LogElapsed("resource.tencentcloud_emr_cluster.update")() logId := tccommon.GetLogId(tccommon.ContextNil) ctx := context.WithValue(context.TODO(), tccommon.LogIdKey, logId) + + immutableFields := []string{"placement", "placement_info", "display_strategy", "login_settings"} + for _, f := range immutableFields { + if d.HasChange(f) { + return fmt.Errorf("cannot update argument `%s`", f) + } + } + emrService := EMRService{ client: meta.(tccommon.ProviderMeta).GetAPIV3Conn(), } @@ -253,13 +294,20 @@ func resourceTencentCloudEmrClusterUpdate(d *schema.ResourceData, meta interface if err != nil { return err } - return nil + return resourceTencentCloudEmrClusterRead(d, meta) } func resourceTencentCloudEmrClusterCreate(d *schema.ResourceData, meta interface{}) error { defer tccommon.LogElapsed("resource.tencentcloud_emr_cluster.create")() logId := tccommon.GetLogId(tccommon.ContextNil) ctx := context.WithValue(context.TODO(), tccommon.LogIdKey, logId) + immutableFields := []string{"time_unit", "time_span", "login_settings"} + for _, f := range immutableFields { + if _, ok := d.GetOkExists(f); !ok { + return fmt.Errorf("Argument `%s` must be set", f) + } + } + emrService := EMRService{ client: meta.(tccommon.ProviderMeta).GetAPIV3Conn(), } @@ -272,6 +320,8 @@ func resourceTencentCloudEmrClusterCreate(d *schema.ResourceData, meta interface var displayStrategy string if v, ok := d.GetOk("display_strategy"); ok { displayStrategy = v.(string) + } else { + displayStrategy = "clusterList" } err = resource.Retry(10*tccommon.ReadRetryTimeout, func() *resource.RetryError { clusters, err := emrService.DescribeInstancesById(ctx, instanceId, displayStrategy) @@ -299,7 +349,7 @@ func resourceTencentCloudEmrClusterCreate(d *schema.ResourceData, meta interface return err } - return nil + return resourceTencentCloudEmrClusterRead(d, meta) } func resourceTencentCloudEmrClusterDelete(d *schema.ResourceData, meta interface{}) error { @@ -376,8 +426,9 @@ func resourceTencentCloudEmrClusterRead(d *schema.ResourceData, meta interface{} client: meta.(tccommon.ProviderMeta).GetAPIV3Conn(), } instanceId := d.Id() + var instance *emr.ClusterInstancesInfo err := resource.Retry(tccommon.ReadRetryTimeout, func() *resource.RetryError { - _, err := emrService.DescribeInstancesById(ctx, instanceId, DisplayStrategyIsclusterList) + result, err := emrService.DescribeInstancesById(ctx, instanceId, DisplayStrategyIsclusterList) if e, ok := err.(*errors.TencentCloudSDKError); ok { if e.GetCode() == "InternalError.ClusterNotFound" { @@ -388,12 +439,191 @@ func resourceTencentCloudEmrClusterRead(d *schema.ResourceData, meta interface{} if err != nil { return resource.RetryableError(err) } + + if len(result) > 0 { + instance = result[0] + } + return nil }) + if err != nil { return err } + _ = d.Set("instance_id", instanceId) + if instance != nil { + _ = d.Set("product_id", instance.ProductId) + _ = d.Set("vpc_settings", map[string]interface{}{ + "vpc_id": *instance.UniqVpcId, + "subnet_id": *instance.UniqSubnetId, + }) + if instance.Config != nil { + if instance.Config.SoftInfo != nil { + _ = d.Set("softwares", helper.PStrings(instance.Config.SoftInfo)) + } + + if instance.Config.SupportHA != nil { + if *instance.Config.SupportHA { + _ = d.Set("support_ha", 1) + } else { + _ = d.Set("support_ha", 0) + } + } + + if instance.Config.SecurityGroup != nil { + _ = d.Set("sg_id", instance.Config.SecurityGroup) + } + resourceSpec := make(map[string]interface{}) + + var masterCount int64 + if instance.Config.MasterNodeSize != nil { + masterCount = *instance.Config.MasterNodeSize + resourceSpec["master_count"] = masterCount + } + if masterCount != 0 && instance.Config.MasterResource != nil { + masterResource := instance.Config.MasterResource + masterResourceSpec := make(map[string]interface{}) + if masterResource.MemSize != nil { + masterResourceSpec["mem_size"] = *masterResource.MemSize + } + if masterResource.Cpu != nil { + masterResourceSpec["cpu"] = *masterResource.Cpu + } + if masterResource.DiskSize != nil { + masterResourceSpec["disk_size"] = *masterResource.DiskSize + } + if masterResource.DiskType != nil { + masterResourceSpec["disk_type"] = *masterResource.DiskType + } + if masterResource.Spec != nil { + masterResourceSpec["spec"] = *masterResource.Spec + } + if masterResource.StorageType != nil { + masterResourceSpec["storage_type"] = *masterResource.StorageType + } + if masterResource.RootSize != nil { + masterResourceSpec["root_size"] = *masterResource.RootSize + } + resourceSpec["master_resource_spec"] = []interface{}{masterResourceSpec} + } + + var coreCount int64 + if instance.Config.CoreNodeSize != nil { + coreCount = *instance.Config.CoreNodeSize + resourceSpec["core_count"] = coreCount + } + if coreCount != 0 && instance.Config.CoreResource != nil { + coreResource := instance.Config.CoreResource + coreResourceSpec := make(map[string]interface{}) + if coreResource.MemSize != nil { + coreResourceSpec["mem_size"] = *coreResource.MemSize + } + if coreResource.Cpu != nil { + coreResourceSpec["cpu"] = *coreResource.Cpu + } + if coreResource.DiskSize != nil { + coreResourceSpec["disk_size"] = *coreResource.DiskSize + } + if coreResource.DiskType != nil { + coreResourceSpec["disk_type"] = *coreResource.DiskType + } + if coreResource.Spec != nil { + coreResourceSpec["spec"] = *coreResource.Spec + } + if coreResource.StorageType != nil { + coreResourceSpec["storage_type"] = *coreResource.StorageType + } + if coreResource.RootSize != nil { + coreResourceSpec["root_size"] = *coreResource.RootSize + } + resourceSpec["core_resource_spec"] = []interface{}{coreResourceSpec} + } + + var taskCount int64 + if instance.Config.TaskNodeSize != nil { + taskCount = *instance.Config.TaskNodeSize + resourceSpec["task_count"] = taskCount + } + if taskCount != 0 && instance.Config.TaskResource != nil { + taskResource := instance.Config.TaskResource + taskResourceSpec := make(map[string]interface{}) + if taskResource.MemSize != nil { + taskResourceSpec["mem_size"] = *taskResource.MemSize + } + if taskResource.Cpu != nil { + taskResourceSpec["cpu"] = *taskResource.Cpu + } + if taskResource.DiskSize != nil { + taskResourceSpec["disk_size"] = *taskResource.DiskSize + } + if taskResource.DiskType != nil { + taskResourceSpec["disk_type"] = *taskResource.DiskType + } + if taskResource.Spec != nil { + taskResourceSpec["spec"] = *taskResource.Spec + } + if taskResource.StorageType != nil { + taskResourceSpec["storage_type"] = *taskResource.StorageType + } + if taskResource.RootSize != nil { + taskResourceSpec["root_size"] = *taskResource.RootSize + } + resourceSpec["task_resource_spec"] = []interface{}{taskResourceSpec} + } + + var commonCount int64 + if instance.Config.ComNodeSize != nil { + commonCount = *instance.Config.ComNodeSize + resourceSpec["common_count"] = commonCount + } + if commonCount != 0 && instance.Config.ComResource != nil { + comResource := instance.Config.ComResource + comResourceSpec := make(map[string]interface{}) + if comResource.MemSize != nil { + comResourceSpec["mem_size"] = *comResource.MemSize + } + if comResource.Cpu != nil { + comResourceSpec["cpu"] = *comResource.Cpu + } + if comResource.DiskSize != nil { + comResourceSpec["disk_size"] = *comResource.DiskSize + } + if comResource.DiskType != nil { + comResourceSpec["disk_type"] = *comResource.DiskType + } + if comResource.Spec != nil { + comResourceSpec["spec"] = *comResource.Spec + } + if comResource.StorageType != nil { + comResourceSpec["storage_type"] = *comResource.StorageType + } + if comResource.RootSize != nil { + comResourceSpec["root_size"] = *comResource.RootSize + } + resourceSpec["common_resource_spec"] = []interface{}{comResourceSpec} + } + + _ = d.Set("resource_spec", []interface{}{resourceSpec}) + } + + _ = d.Set("instance_name", instance.ClusterName) + _ = d.Set("pay_mode", instance.ChargeType) + placement := map[string]interface{}{ + "zone": *instance.Zone, + "project_id": *instance.ProjectId, + } + _ = d.Set("placement", map[string]interface{}{ + "zone": *instance.Zone, + }) + _ = d.Set("placement_info", []interface{}{placement}) + if instance.MasterIp != nil { + _ = d.Set("need_master_wan", "NEED_MASTER_WAN") + } else { + _ = d.Set("need_master_wan", "NOT_NEED_MASTER_WAN") + } + } + tagService := svctag.NewTagService(meta.(tccommon.ProviderMeta).GetAPIV3Conn()) region := meta.(tccommon.ProviderMeta).GetAPIV3Conn().Region tags, err := tagService.DescribeResourceTags(ctx, "emr", "emr-instance", region, d.Id()) diff --git a/tencentcloud/services/emr/resource_tc_emr_cluster.md b/tencentcloud/services/emr/resource_tc_emr_cluster.md index 50b66f4a6d..53c57ff5d6 100644 --- a/tencentcloud/services/emr/resource_tc_emr_cluster.md +++ b/tencentcloud/services/emr/resource_tc_emr_cluster.md @@ -41,15 +41,18 @@ resource "tencentcloud_security_group" "emr_sg" { } resource "tencentcloud_emr_cluster" "emr_cluster" { - product_id=4 - display_strategy="clusterList" + product_id=38 vpc_settings={ vpc_id=tencentcloud_vpc.emr_vpc.id subnet_id=tencentcloud_subnet.emr_subnet.id } - softwares=[ - "zookeeper-3.6.1", - ] + softwares = [ + "hdfs-2.8.5", + "knox-1.6.1", + "openldap-2.4.44", + "yarn-2.8.5", + "zookeeper-3.6.3", + ] support_ha=0 instance_name="emr-cluster-test" resource_spec { @@ -80,7 +83,7 @@ resource "tencentcloud_emr_cluster" "emr_cluster" { time_span=3600 time_unit="s" pay_mode=0 - placement={ + placement_info={ zone=var.availability_zone project_id=0 } diff --git a/tencentcloud/services/emr/resource_tc_emr_cluster_test.go b/tencentcloud/services/emr/resource_tc_emr_cluster_test.go index 11c0e943d5..714f38ff98 100644 --- a/tencentcloud/services/emr/resource_tc_emr_cluster_test.go +++ b/tencentcloud/services/emr/resource_tc_emr_cluster_test.go @@ -122,11 +122,10 @@ func TestAccTencentCloudEmrClusterResource(t *testing.T) { Config: testEmrBasic, Check: resource.ComposeTestCheckFunc( testAccCheckEmrExists(testEmrClusterResourceKey), - resource.TestCheckResourceAttr(testEmrClusterResourceKey, "product_id", "4"), - resource.TestCheckResourceAttr(testEmrClusterResourceKey, "display_strategy", "clusterList"), + resource.TestCheckResourceAttr(testEmrClusterResourceKey, "product_id", "38"), resource.TestCheckResourceAttr(testEmrClusterResourceKey, "vpc_settings.vpc_id", tcacctest.DefaultEMRVpcId), resource.TestCheckResourceAttr(testEmrClusterResourceKey, "vpc_settings.subnet_id", tcacctest.DefaultEMRSubnetId), - resource.TestCheckResourceAttr(testEmrClusterResourceKey, "softwares.0", "zookeeper-3.6.1"), + resource.TestCheckResourceAttr(testEmrClusterResourceKey, "softwares.#", "5"), resource.TestCheckResourceAttr(testEmrClusterResourceKey, "support_ha", "0"), resource.TestCheckResourceAttr(testEmrClusterResourceKey, "instance_name", "emr-test-demo"), resource.TestCheckResourceAttr(testEmrClusterResourceKey, "resource_spec.#", "1"), @@ -134,13 +133,18 @@ func TestAccTencentCloudEmrClusterResource(t *testing.T) { resource.TestCheckResourceAttr(testEmrClusterResourceKey, "time_span", "3600"), resource.TestCheckResourceAttr(testEmrClusterResourceKey, "time_unit", "s"), resource.TestCheckResourceAttr(testEmrClusterResourceKey, "pay_mode", "0"), - resource.TestCheckResourceAttr(testEmrClusterResourceKey, "placement.zone", "ap-guangzhou-3"), - resource.TestCheckResourceAttr(testEmrClusterResourceKey, "placement.project_id", "0"), + resource.TestCheckResourceAttr(testEmrClusterResourceKey, "placement_info.0.zone", "ap-guangzhou-3"), + resource.TestCheckResourceAttr(testEmrClusterResourceKey, "placement_info.0.project_id", "0"), resource.TestCheckResourceAttrSet(testEmrClusterResourceKey, "instance_id"), resource.TestCheckResourceAttr(testEmrClusterResourceKey, "sg_id", tcacctest.DefaultEMRSgId), resource.TestCheckResourceAttr(testEmrClusterResourceKey, "tags.emr-key", "emr-value"), ), }, + { + ResourceName: testEmrClusterResourceKey, + ImportState: true, + ImportStateVerifyIgnore: []string{"display_strategy", "placement", "time_span", "time_unit", "login_settings"}, + }, }, }) } @@ -203,15 +207,18 @@ data "tencentcloud_instance_types" "cvm4c8m" { } resource "tencentcloud_emr_cluster" "emrrrr" { - product_id=4 - display_strategy="clusterList" + product_id=38 vpc_settings={ vpc_id=var.vpc_id subnet_id=var.subnet_id } - softwares=[ - "zookeeper-3.6.1", - ] + softwares = [ + "hdfs-2.8.5", + "knox-1.6.1", + "openldap-2.4.44", + "yarn-2.8.5", + "zookeeper-3.6.3", + ] support_ha=0 instance_name="emr-test-demo" resource_spec { @@ -242,7 +249,7 @@ resource "tencentcloud_emr_cluster" "emrrrr" { time_span=3600 time_unit="s" pay_mode=0 - placement={ + placement_info { zone="ap-guangzhou-3" project_id=0 } diff --git a/tencentcloud/services/emr/service_tencentcloud_emr.go b/tencentcloud/services/emr/service_tencentcloud_emr.go index 5c67ba3eca..1d27480109 100644 --- a/tencentcloud/services/emr/service_tencentcloud_emr.go +++ b/tencentcloud/services/emr/service_tencentcloud_emr.go @@ -80,7 +80,7 @@ func (me *EMRService) CreateInstance(ctx context.Context, d *schema.ResourceData } if v, ok := d.GetOk("softwares"); ok { - softwares := v.([]interface{}) + softwares := v.(*schema.Set).List() request.Software = make([]*string, 0) for _, software := range softwares { request.Software = append(request.Software, common.StringPtr(software.(string))) @@ -149,6 +149,22 @@ func (me *EMRService) CreateInstance(ctx context.Context, d *schema.ResourceData } } + if v, ok := d.GetOk("placement_info"); ok { + request.Placement = &emr.Placement{} + placementList := v.([]interface{}) + placement := placementList[0].(map[string]interface{}) + + if v, ok := placement["project_id"]; ok { + projectId := v.(int) + request.Placement.ProjectId = helper.IntInt64(projectId) + } else { + request.Placement.ProjectId = helper.IntInt64(0) + } + if zone, ok := placement["zone"]; ok { + request.Placement.Zone = common.StringPtr(zone.(string)) + } + } + if v, ok := d.GetOk("time_span"); ok { request.TimeSpan = common.Uint64Ptr((uint64)(v.(int))) } diff --git a/website/docs/r/emr_cluster.html.markdown b/website/docs/r/emr_cluster.html.markdown index 5245fa89d8..54ff40449d 100644 --- a/website/docs/r/emr_cluster.html.markdown +++ b/website/docs/r/emr_cluster.html.markdown @@ -52,14 +52,17 @@ resource "tencentcloud_security_group" "emr_sg" { } resource "tencentcloud_emr_cluster" "emr_cluster" { - product_id = 4 - display_strategy = "clusterList" + product_id = 38 vpc_settings = { vpc_id = tencentcloud_vpc.emr_vpc.id subnet_id = tencentcloud_subnet.emr_subnet.id } softwares = [ - "zookeeper-3.6.1", + "hdfs-2.8.5", + "knox-1.6.1", + "openldap-2.4.44", + "yarn-2.8.5", + "zookeeper-3.6.3", ] support_ha = 0 instance_name = "emr-cluster-test" @@ -91,7 +94,7 @@ resource "tencentcloud_emr_cluster" "emr_cluster" { time_span = 3600 time_unit = "s" pay_mode = 0 - placement = { + placement_info = { zone = var.availability_zone project_id = 0 } @@ -103,11 +106,8 @@ resource "tencentcloud_emr_cluster" "emr_cluster" { The following arguments are supported: -* `display_strategy` - (Required, String, ForceNew) Display strategy of EMR instance. * `instance_name` - (Required, String, ForceNew) Name of the instance, which can contain 6 to 36 English letters, Chinese characters, digits, dashes(-), or underscores(_). -* `login_settings` - (Required, Map, ForceNew) Instance login settings. * `pay_mode` - (Required, Int) The pay mode of instance. 0 represent POSTPAID_BY_HOUR, 1 represent PREPAID. -* `placement` - (Required, Map, ForceNew) The location of the instance. * `product_id` - (Required, Int, ForceNew) Product ID. Different products ID represents different EMR product versions. Value range: - 16: represents EMR-V2.3.0 - 20: indicates EMR-V2.5.0 @@ -121,20 +121,29 @@ The following arguments are supported: - 38: represents EMR-V2.7.0 - 39: stands for STARROCKS-V1.1.0 - 41: represents DRUID-V1.1.0. -* `softwares` - (Required, List: [`String`], ForceNew) The softwares of a EMR instance. +* `softwares` - (Required, Set: [`String`], ForceNew) The softwares of a EMR instance. * `support_ha` - (Required, Int, ForceNew) The flag whether the instance support high availability.(0=>not support, 1=>support). -* `time_span` - (Required, Int) The length of time the instance was purchased. Use with TimeUnit.When TimeUnit is s, the parameter can only be filled in at 3600, representing a metered instance. -When TimeUnit is m, the number filled in by this parameter indicates the length of purchase of the monthly instance of the package year, such as 1 for one month of purchase. -* `time_unit` - (Required, String) The unit of time in which the instance was purchased. When PayMode is 0, TimeUnit can only take values of s(second). When PayMode is 1, TimeUnit can only take the value m(month). * `vpc_settings` - (Required, Map, ForceNew) The private net config of EMR instance. +* `display_strategy` - (Optional, String, **Deprecated**) It will be deprecated in later versions. Display strategy of EMR instance. * `extend_fs_field` - (Optional, String) Access the external file system. +* `login_settings` - (Optional, Map) Instance login settings. * `need_master_wan` - (Optional, String, ForceNew) Whether to enable the cluster Master node public network. Value range: - NEED_MASTER_WAN: Indicates that the cluster Master node public network is enabled. - NOT_NEED_MASTER_WAN: Indicates that it is not turned on. By default, the cluster Master node internet is enabled. +* `placement_info` - (Optional, List) The location of the instance. +* `placement` - (Optional, Map, **Deprecated**) It will be deprecated in later versions. Use `placement_info` instead. The location of the instance. * `resource_spec` - (Optional, List) Resource specification of EMR instance. * `sg_id` - (Optional, String, ForceNew) The ID of the security group to which the instance belongs, in the form of sg-xxxxxxxx. * `tags` - (Optional, Map) Tag description list. +* `time_span` - (Optional, Int) The length of time the instance was purchased. Use with TimeUnit.When TimeUnit is s, the parameter can only be filled in at 3600, representing a metered instance. +When TimeUnit is m, the number filled in by this parameter indicates the length of purchase of the monthly instance of the package year, such as 1 for one month of purchase. +* `time_unit` - (Optional, String) The unit of time in which the instance was purchased. When PayMode is 0, TimeUnit can only take values of s(second). When PayMode is 1, TimeUnit can only take the value m(month). + +The `placement_info` object supports the following: + +* `zone` - (Required, String) Zone. +* `project_id` - (Optional, Int) Project id. The `resource_spec` object supports the following: