github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/builtin/providers/alicloud/resource_alicloud_instance.go (about)

     1  package alicloud
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"github.com/denverdino/aliyungo/common"
    10  	"github.com/denverdino/aliyungo/ecs"
    11  	"github.com/hashicorp/terraform/helper/resource"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  	"strings"
    14  	"time"
    15  )
    16  
    17  func resourceAliyunInstance() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAliyunInstanceCreate,
    20  		Read:   resourceAliyunInstanceRead,
    21  		Update: resourceAliyunInstanceUpdate,
    22  		Delete: resourceAliyunInstanceDelete,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"availability_zone": &schema.Schema{
    26  				Type:     schema.TypeString,
    27  				Optional: true,
    28  				ForceNew: true,
    29  				Computed: true,
    30  			},
    31  
    32  			"image_id": &schema.Schema{
    33  				Type:     schema.TypeString,
    34  				Required: true,
    35  			},
    36  
    37  			"instance_type": &schema.Schema{
    38  				Type:     schema.TypeString,
    39  				Required: true,
    40  			},
    41  
    42  			"security_groups": &schema.Schema{
    43  				Type:     schema.TypeSet,
    44  				Elem:     &schema.Schema{Type: schema.TypeString},
    45  				Required: true,
    46  			},
    47  
    48  			"allocate_public_ip": &schema.Schema{
    49  				Type:     schema.TypeBool,
    50  				Optional: true,
    51  				Default:  false,
    52  			},
    53  
    54  			"instance_name": &schema.Schema{
    55  				Type:         schema.TypeString,
    56  				Optional:     true,
    57  				Default:      "ECS-Instance",
    58  				ValidateFunc: validateInstanceName,
    59  			},
    60  
    61  			"description": &schema.Schema{
    62  				Type:         schema.TypeString,
    63  				Optional:     true,
    64  				ValidateFunc: validateInstanceDescription,
    65  			},
    66  
    67  			"internet_charge_type": &schema.Schema{
    68  				Type:         schema.TypeString,
    69  				Optional:     true,
    70  				ForceNew:     true,
    71  				ValidateFunc: validateInternetChargeType,
    72  			},
    73  			"internet_max_bandwidth_in": &schema.Schema{
    74  				Type:     schema.TypeString,
    75  				Optional: true,
    76  				ForceNew: true,
    77  			},
    78  			"internet_max_bandwidth_out": &schema.Schema{
    79  				Type:         schema.TypeInt,
    80  				Optional:     true,
    81  				ForceNew:     true,
    82  				ValidateFunc: validateInternetMaxBandWidthOut,
    83  			},
    84  			"host_name": &schema.Schema{
    85  				Type:     schema.TypeString,
    86  				Optional: true,
    87  				Computed: true,
    88  			},
    89  			"password": &schema.Schema{
    90  				Type:      schema.TypeString,
    91  				Optional:  true,
    92  				Sensitive: true,
    93  			},
    94  			"io_optimized": &schema.Schema{
    95  				Type:         schema.TypeString,
    96  				Required:     true,
    97  				ForceNew:     true,
    98  				ValidateFunc: validateIoOptimized,
    99  			},
   100  
   101  			"system_disk_category": &schema.Schema{
   102  				Type:     schema.TypeString,
   103  				Default:  "cloud",
   104  				Optional: true,
   105  				ForceNew: true,
   106  				ValidateFunc: validateAllowedStringValue([]string{
   107  					string(ecs.DiskCategoryCloud),
   108  					string(ecs.DiskCategoryCloudSSD),
   109  					string(ecs.DiskCategoryCloudEfficiency),
   110  					string(ecs.DiskCategoryEphemeralSSD),
   111  				}),
   112  			},
   113  			"system_disk_size": &schema.Schema{
   114  				Type:         schema.TypeInt,
   115  				Optional:     true,
   116  				Computed:     true,
   117  				ValidateFunc: validateIntegerInRange(40, 500),
   118  			},
   119  
   120  			//subnet_id and vswitch_id both exists, cause compatible old version, and aws habit.
   121  			"subnet_id": &schema.Schema{
   122  				Type:     schema.TypeString,
   123  				Optional: true,
   124  				ForceNew: true,
   125  				Computed: true, //add this schema cause subnet_id not used enter parameter, will different, so will be ForceNew
   126  			},
   127  
   128  			"vswitch_id": &schema.Schema{
   129  				Type:     schema.TypeString,
   130  				Optional: true,
   131  				ForceNew: true,
   132  			},
   133  
   134  			"instance_charge_type": &schema.Schema{
   135  				Type:         schema.TypeString,
   136  				Optional:     true,
   137  				ForceNew:     true,
   138  				ValidateFunc: validateInstanceChargeType,
   139  			},
   140  			"period": &schema.Schema{
   141  				Type:     schema.TypeInt,
   142  				Optional: true,
   143  				ForceNew: true,
   144  			},
   145  
   146  			"public_ip": &schema.Schema{
   147  				Type:     schema.TypeString,
   148  				Optional: true,
   149  				Computed: true,
   150  			},
   151  
   152  			"private_ip": &schema.Schema{
   153  				Type:     schema.TypeString,
   154  				Computed: true,
   155  			},
   156  
   157  			"status": &schema.Schema{
   158  				Type:     schema.TypeString,
   159  				Computed: true,
   160  			},
   161  
   162  			"user_data": &schema.Schema{
   163  				Type:     schema.TypeString,
   164  				Optional: true,
   165  				ForceNew: true,
   166  			},
   167  
   168  			"tags": tagsSchema(),
   169  		},
   170  	}
   171  }
   172  
   173  func resourceAliyunInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   174  	conn := meta.(*AliyunClient).ecsconn
   175  
   176  	// create postpaid instance by runInstances API
   177  	if v := d.Get("instance_charge_type").(string); v != string(common.PrePaid) {
   178  		return resourceAliyunRunInstance(d, meta)
   179  	}
   180  
   181  	args, err := buildAliyunInstanceArgs(d, meta)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	instanceID, err := conn.CreateInstance(args)
   187  	if err != nil {
   188  		return fmt.Errorf("Error creating Aliyun ecs instance: %#v", err)
   189  	}
   190  
   191  	d.SetId(instanceID)
   192  
   193  	d.Set("password", d.Get("password"))
   194  
   195  	// after instance created, its status is pending,
   196  	// so we need to wait it become to stopped and then start it
   197  	if err := conn.WaitForInstanceAsyn(d.Id(), ecs.Stopped, defaultTimeout); err != nil {
   198  		return fmt.Errorf("[DEBUG] WaitForInstance %s got error: %#v", ecs.Stopped, err)
   199  	}
   200  
   201  	if err := allocateIpAndBandWidthRelative(d, meta); err != nil {
   202  		return fmt.Errorf("allocateIpAndBandWidthRelative err: %#v", err)
   203  	}
   204  
   205  	if err := conn.StartInstance(d.Id()); err != nil {
   206  		return fmt.Errorf("Start instance got error: %#v", err)
   207  	}
   208  
   209  	if err := conn.WaitForInstanceAsyn(d.Id(), ecs.Running, defaultTimeout); err != nil {
   210  		return fmt.Errorf("[DEBUG] WaitForInstance %s got error: %#v", ecs.Running, err)
   211  	}
   212  
   213  	return resourceAliyunInstanceUpdate(d, meta)
   214  }
   215  
   216  func resourceAliyunRunInstance(d *schema.ResourceData, meta interface{}) error {
   217  	conn := meta.(*AliyunClient).ecsconn
   218  	newConn := meta.(*AliyunClient).ecsNewconn
   219  
   220  	args, err := buildAliyunInstanceArgs(d, meta)
   221  	if err != nil {
   222  		return err
   223  	}
   224  
   225  	if args.IoOptimized == "optimized" {
   226  		args.IoOptimized = ecs.IoOptimized("true")
   227  	} else {
   228  		args.IoOptimized = ecs.IoOptimized("false")
   229  	}
   230  
   231  	runArgs, err := buildAliyunRunInstancesArgs(d, meta)
   232  	if err != nil {
   233  		return err
   234  	}
   235  
   236  	runArgs.CreateInstanceArgs = *args
   237  
   238  	// runInstances is support in version 2016-03-14
   239  	instanceIds, err := newConn.RunInstances(runArgs)
   240  
   241  	if err != nil {
   242  		return fmt.Errorf("Error creating Aliyun ecs instance: %#v", err)
   243  	}
   244  
   245  	d.SetId(instanceIds[0])
   246  
   247  	d.Set("password", d.Get("password"))
   248  	d.Set("system_disk_category", d.Get("system_disk_category"))
   249  	d.Set("system_disk_size", d.Get("system_disk_size"))
   250  
   251  	// after instance created, its status change from pending, starting to running
   252  	if err := conn.WaitForInstanceAsyn(d.Id(), ecs.Running, defaultTimeout); err != nil {
   253  		return fmt.Errorf("[DEBUG] WaitForInstance %s got error: %#v", ecs.Running, err)
   254  	}
   255  
   256  	if err := allocateIpAndBandWidthRelative(d, meta); err != nil {
   257  		return fmt.Errorf("allocateIpAndBandWidthRelative err: %#v", err)
   258  	}
   259  
   260  	if err := conn.WaitForInstanceAsyn(d.Id(), ecs.Running, defaultTimeout); err != nil {
   261  		return fmt.Errorf("[DEBUG] WaitForInstance %s got error: %#v", ecs.Running, err)
   262  	}
   263  
   264  	return resourceAliyunInstanceUpdate(d, meta)
   265  }
   266  
   267  func resourceAliyunInstanceRead(d *schema.ResourceData, meta interface{}) error {
   268  	client := meta.(*AliyunClient)
   269  	conn := client.ecsconn
   270  
   271  	instance, err := client.QueryInstancesById(d.Id())
   272  
   273  	if err != nil {
   274  		if notFoundError(err) {
   275  			d.SetId("")
   276  			return nil
   277  		}
   278  		return fmt.Errorf("Error DescribeInstanceAttribute: %#v", err)
   279  	}
   280  
   281  	disk, diskErr := client.QueryInstanceSystemDisk(d.Id())
   282  
   283  	if diskErr != nil {
   284  		if notFoundError(diskErr) {
   285  			d.SetId("")
   286  			return nil
   287  		}
   288  		return fmt.Errorf("Error DescribeSystemDisk: %#v", err)
   289  	}
   290  
   291  	d.Set("instance_name", instance.InstanceName)
   292  	d.Set("description", instance.Description)
   293  	d.Set("status", instance.Status)
   294  	d.Set("availability_zone", instance.ZoneId)
   295  	d.Set("host_name", instance.HostName)
   296  	d.Set("image_id", instance.ImageId)
   297  	d.Set("instance_type", instance.InstanceType)
   298  	d.Set("system_disk_category", disk.Category)
   299  	d.Set("system_disk_size", disk.Size)
   300  
   301  	// In Classic network, internet_charge_type is valid in any case, and its default value is 'PayByBanwidth'.
   302  	// In VPC network, internet_charge_type is valid when instance has public ip, and its default value is 'PayByBanwidth'.
   303  	d.Set("internet_charge_type", instance.InternetChargeType)
   304  
   305  	if d.Get("allocate_public_ip").(bool) {
   306  		d.Set("public_ip", instance.PublicIpAddress.IpAddress[0])
   307  	}
   308  
   309  	if ecs.StringOrBool(instance.IoOptimized).Value {
   310  		d.Set("io_optimized", "optimized")
   311  	} else {
   312  		d.Set("io_optimized", "none")
   313  	}
   314  
   315  	if d.Get("subnet_id").(string) != "" || d.Get("vswitch_id").(string) != "" {
   316  		ipAddress := instance.VpcAttributes.PrivateIpAddress.IpAddress[0]
   317  		d.Set("private_ip", ipAddress)
   318  		d.Set("subnet_id", instance.VpcAttributes.VSwitchId)
   319  		d.Set("vswitch_id", instance.VpcAttributes.VSwitchId)
   320  	} else {
   321  		ipAddress := strings.Join(ecs.IpAddressSetType(instance.InnerIpAddress).IpAddress, ",")
   322  		d.Set("private_ip", ipAddress)
   323  	}
   324  
   325  	if d.Get("user_data").(string) != "" {
   326  		ud, err := conn.DescribeUserdata(&ecs.DescribeUserdataArgs{
   327  			RegionId:   getRegion(d, meta),
   328  			InstanceId: d.Id(),
   329  		})
   330  
   331  		if err != nil {
   332  			log.Printf("[ERROR] DescribeUserData for instance got error: %#v", err)
   333  		}
   334  		d.Set("user_data", userDataHashSum(ud.UserData))
   335  	}
   336  
   337  	tags, _, err := conn.DescribeTags(&ecs.DescribeTagsArgs{
   338  		RegionId:     getRegion(d, meta),
   339  		ResourceType: ecs.TagResourceInstance,
   340  		ResourceId:   d.Id(),
   341  	})
   342  
   343  	if err != nil {
   344  		log.Printf("[ERROR] DescribeTags for instance got error: %#v", err)
   345  	}
   346  	d.Set("tags", tagsToMap(tags))
   347  
   348  	return nil
   349  }
   350  
   351  func resourceAliyunInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
   352  
   353  	client := meta.(*AliyunClient)
   354  	conn := client.ecsconn
   355  
   356  	d.Partial(true)
   357  
   358  	if err := setTags(client, ecs.TagResourceInstance, d); err != nil {
   359  		log.Printf("[DEBUG] Set tags for instance got error: %#v", err)
   360  		return fmt.Errorf("Set tags for instance got error: %#v", err)
   361  	} else {
   362  		d.SetPartial("tags")
   363  	}
   364  
   365  	imageUpdate := false
   366  	if d.HasChange("image_id") && !d.IsNewResource() {
   367  		log.Printf("[DEBUG] Replace instance system disk via changing image_id")
   368  		replaceSystemArgs := &ecs.ReplaceSystemDiskArgs{
   369  			InstanceId: d.Id(),
   370  			ImageId:    d.Get("image_id").(string),
   371  			SystemDisk: ecs.SystemDiskType{
   372  				Size: d.Get("system_disk_size").(int),
   373  			},
   374  		}
   375  		if v, ok := d.GetOk("status"); ok && v.(string) != "" {
   376  			if ecs.InstanceStatus(d.Get("status").(string)) == ecs.Running {
   377  				log.Printf("[DEBUG] StopInstance before change system disk")
   378  				if err := conn.StopInstance(d.Id(), true); err != nil {
   379  					return fmt.Errorf("Force Stop Instance got an error: %#v", err)
   380  				}
   381  				if err := conn.WaitForInstance(d.Id(), ecs.Stopped, 60); err != nil {
   382  					return fmt.Errorf("WaitForInstance got error: %#v", err)
   383  				}
   384  			}
   385  		}
   386  		_, err := conn.ReplaceSystemDisk(replaceSystemArgs)
   387  		if err != nil {
   388  			return fmt.Errorf("Replace system disk got an error: %#v", err)
   389  		}
   390  		// Ensure instance's image has been replaced successfully.
   391  		timeout := ecs.InstanceDefaultTimeout
   392  		for {
   393  			instance, errDesc := conn.DescribeInstanceAttribute(d.Id())
   394  			if errDesc != nil {
   395  				return fmt.Errorf("Describe instance got an error: %#v", errDesc)
   396  			}
   397  			if instance.ImageId == d.Get("image_id") {
   398  				break
   399  			}
   400  			time.Sleep(ecs.DefaultWaitForInterval * time.Second)
   401  			timeout = timeout - ecs.DefaultWaitForInterval
   402  			if timeout <= 0 {
   403  				return common.GetClientErrorFromString("Timeout")
   404  			}
   405  		}
   406  		imageUpdate = true
   407  		d.SetPartial("system_disk_size")
   408  		d.SetPartial("image_id")
   409  	}
   410  	// Provider doesn't support change 'system_disk_size'separately.
   411  	if d.HasChange("system_disk_size") && !d.HasChange("image_id") {
   412  		return fmt.Errorf("Update resource failed. 'system_disk_size' isn't allowed to change separately. You can update it via renewing instance or replacing system disk.")
   413  	}
   414  
   415  	attributeUpdate := false
   416  	args := &ecs.ModifyInstanceAttributeArgs{
   417  		InstanceId: d.Id(),
   418  	}
   419  
   420  	if d.HasChange("instance_name") && !d.IsNewResource() {
   421  		log.Printf("[DEBUG] ModifyInstanceAttribute instance_name")
   422  		d.SetPartial("instance_name")
   423  		args.InstanceName = d.Get("instance_name").(string)
   424  
   425  		attributeUpdate = true
   426  	}
   427  
   428  	if d.HasChange("description") && !d.IsNewResource() {
   429  		log.Printf("[DEBUG] ModifyInstanceAttribute description")
   430  		d.SetPartial("description")
   431  		args.Description = d.Get("description").(string)
   432  
   433  		attributeUpdate = true
   434  	}
   435  
   436  	if d.HasChange("host_name") && !d.IsNewResource() {
   437  		log.Printf("[DEBUG] ModifyInstanceAttribute host_name")
   438  		d.SetPartial("host_name")
   439  		args.HostName = d.Get("host_name").(string)
   440  
   441  		attributeUpdate = true
   442  	}
   443  
   444  	passwordUpdate := false
   445  	if d.HasChange("password") && !d.IsNewResource() {
   446  		log.Printf("[DEBUG] ModifyInstanceAttribute password")
   447  		d.SetPartial("password")
   448  		args.Password = d.Get("password").(string)
   449  
   450  		attributeUpdate = true
   451  		passwordUpdate = true
   452  	}
   453  
   454  	if attributeUpdate {
   455  		if err := conn.ModifyInstanceAttribute(args); err != nil {
   456  			return fmt.Errorf("Modify instance attribute got error: %#v", err)
   457  		}
   458  	}
   459  
   460  	if imageUpdate || passwordUpdate {
   461  		instance, errDesc := conn.DescribeInstanceAttribute(d.Id())
   462  		if errDesc != nil {
   463  			return fmt.Errorf("Describe instance got an error: %#v", errDesc)
   464  		}
   465  		if instance.Status != ecs.Running && instance.Status != ecs.Stopped {
   466  			return fmt.Errorf("ECS instance's status doesn't support to start or reboot operation after replace image_id or update password. The current instance's status is %#v", instance.Status)
   467  		} else if instance.Status == ecs.Running {
   468  			log.Printf("[DEBUG] Reboot instance after change image or password")
   469  			if err := conn.RebootInstance(d.Id(), false); err != nil {
   470  				return fmt.Errorf("RebootInstance got error: %#v", err)
   471  			}
   472  		} else {
   473  			log.Printf("[DEBUG] Start instance after change image or password")
   474  			if err := conn.StartInstance(d.Id()); err != nil {
   475  				return fmt.Errorf("StartInstance got error: %#v", err)
   476  			}
   477  		}
   478  		// Start instance sometimes costs more than 6 minutes when os type is centos.
   479  		if err := conn.WaitForInstance(d.Id(), ecs.Running, 400); err != nil {
   480  			return fmt.Errorf("WaitForInstance got error: %#v", err)
   481  		}
   482  	}
   483  
   484  	if d.HasChange("security_groups") {
   485  		o, n := d.GetChange("security_groups")
   486  		os := o.(*schema.Set)
   487  		ns := n.(*schema.Set)
   488  
   489  		rl := expandStringList(os.Difference(ns).List())
   490  		al := expandStringList(ns.Difference(os).List())
   491  
   492  		if len(al) > 0 {
   493  			err := client.JoinSecurityGroups(d.Id(), al)
   494  			if err != nil {
   495  				return err
   496  			}
   497  		}
   498  		if len(rl) > 0 {
   499  			err := client.LeaveSecurityGroups(d.Id(), rl)
   500  			if err != nil {
   501  				return err
   502  			}
   503  		}
   504  
   505  		d.SetPartial("security_groups")
   506  	}
   507  
   508  	d.Partial(false)
   509  	return resourceAliyunInstanceRead(d, meta)
   510  }
   511  
   512  func resourceAliyunInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   513  	client := meta.(*AliyunClient)
   514  	conn := client.ecsconn
   515  
   516  	return resource.Retry(5*time.Minute, func() *resource.RetryError {
   517  		instance, err := client.QueryInstancesById(d.Id())
   518  		if err != nil {
   519  			if notFoundError(err) {
   520  				return nil
   521  			}
   522  		}
   523  
   524  		if instance.Status != ecs.Stopped {
   525  			if err := conn.StopInstance(d.Id(), true); err != nil {
   526  				return resource.RetryableError(fmt.Errorf("ECS stop error - trying again."))
   527  			}
   528  
   529  			if err := conn.WaitForInstance(d.Id(), ecs.Stopped, defaultTimeout); err != nil {
   530  				return resource.RetryableError(fmt.Errorf("Waiting for ecs stopped timeout - trying again."))
   531  			}
   532  		}
   533  
   534  		if err := conn.DeleteInstance(d.Id()); err != nil {
   535  			return resource.RetryableError(fmt.Errorf("ECS Instance in use - trying again while it is deleted."))
   536  		}
   537  
   538  		return nil
   539  	})
   540  
   541  }
   542  
   543  func allocateIpAndBandWidthRelative(d *schema.ResourceData, meta interface{}) error {
   544  	conn := meta.(*AliyunClient).ecsconn
   545  	if d.Get("allocate_public_ip").(bool) {
   546  		if d.Get("internet_max_bandwidth_out") == 0 {
   547  			return fmt.Errorf("Error: if allocate_public_ip is true than the internet_max_bandwidth_out cannot equal zero.")
   548  		}
   549  		_, err := conn.AllocatePublicIpAddress(d.Id())
   550  		if err != nil {
   551  			return fmt.Errorf("[DEBUG] AllocatePublicIpAddress for instance got error: %#v", err)
   552  		}
   553  	}
   554  	return nil
   555  }
   556  
   557  func buildAliyunRunInstancesArgs(d *schema.ResourceData, meta interface{}) (*ecs.RunInstanceArgs, error) {
   558  	args := &ecs.RunInstanceArgs{
   559  		MaxAmount: DEFAULT_INSTANCE_COUNT,
   560  		MinAmount: DEFAULT_INSTANCE_COUNT,
   561  	}
   562  
   563  	bussStr, err := json.Marshal(DefaultBusinessInfo)
   564  	if err != nil {
   565  		log.Printf("Failed to translate bussiness info %#v from json to string", DefaultBusinessInfo)
   566  	}
   567  
   568  	args.BusinessInfo = string(bussStr)
   569  
   570  	subnetValue := d.Get("subnet_id").(string)
   571  	vswitchValue := d.Get("vswitch_id").(string)
   572  	//networkValue := d.Get("instance_network_type").(string)
   573  
   574  	// because runInstance is not compatible with createInstance, force NetworkType value to classic
   575  	if subnetValue == "" && vswitchValue == "" {
   576  		args.NetworkType = string(ClassicNet)
   577  	}
   578  
   579  	return args, nil
   580  }
   581  
   582  func buildAliyunInstanceArgs(d *schema.ResourceData, meta interface{}) (*ecs.CreateInstanceArgs, error) {
   583  	client := meta.(*AliyunClient)
   584  
   585  	args := &ecs.CreateInstanceArgs{
   586  		RegionId:     getRegion(d, meta),
   587  		InstanceType: d.Get("instance_type").(string),
   588  	}
   589  
   590  	imageID := d.Get("image_id").(string)
   591  
   592  	args.ImageId = imageID
   593  
   594  	systemDiskCategory := ecs.DiskCategory(d.Get("system_disk_category").(string))
   595  	systemDiskSize := d.Get("system_disk_size").(int)
   596  
   597  	zoneID := d.Get("availability_zone").(string)
   598  	// check instanceType and systemDiskCategory, when zoneID is not empty
   599  	if zoneID != "" {
   600  		zone, err := client.DescribeZone(zoneID)
   601  		if err != nil {
   602  			return nil, err
   603  		}
   604  
   605  		if err := client.ResourceAvailable(zone, ecs.ResourceTypeInstance); err != nil {
   606  			return nil, err
   607  		}
   608  
   609  		if err := client.DiskAvailable(zone, systemDiskCategory); err != nil {
   610  			return nil, err
   611  		}
   612  
   613  		args.ZoneId = zoneID
   614  
   615  	}
   616  
   617  	args.SystemDisk = ecs.SystemDiskType{
   618  		Category: systemDiskCategory,
   619  		Size:     systemDiskSize,
   620  	}
   621  
   622  	sgs, ok := d.GetOk("security_groups")
   623  
   624  	if ok {
   625  		sgList := expandStringList(sgs.(*schema.Set).List())
   626  		sg0 := sgList[0]
   627  		// check security group instance exist
   628  		_, err := client.DescribeSecurity(sg0)
   629  		if err == nil {
   630  			args.SecurityGroupId = sg0
   631  		}
   632  	}
   633  
   634  	if v := d.Get("instance_name").(string); v != "" {
   635  		args.InstanceName = v
   636  	}
   637  
   638  	if v := d.Get("description").(string); v != "" {
   639  		args.Description = v
   640  	}
   641  
   642  	if v := d.Get("internet_charge_type").(string); v != "" {
   643  		args.InternetChargeType = common.InternetChargeType(v)
   644  	}
   645  
   646  	if v := d.Get("internet_max_bandwidth_out").(int); v != 0 {
   647  		args.InternetMaxBandwidthOut = v
   648  	}
   649  
   650  	if v := d.Get("host_name").(string); v != "" {
   651  		args.HostName = v
   652  	}
   653  
   654  	if v := d.Get("password").(string); v != "" {
   655  		args.Password = v
   656  	}
   657  
   658  	if v := d.Get("io_optimized").(string); v != "" {
   659  		args.IoOptimized = ecs.IoOptimized(v)
   660  	}
   661  
   662  	vswitchValue := d.Get("subnet_id").(string)
   663  	if vswitchValue == "" {
   664  		vswitchValue = d.Get("vswitch_id").(string)
   665  	}
   666  	if vswitchValue != "" {
   667  		args.VSwitchId = vswitchValue
   668  		if d.Get("allocate_public_ip").(bool) && args.InternetMaxBandwidthOut <= 0 {
   669  			return nil, fmt.Errorf("Invalid internet_max_bandwidth_out result in allocation public ip failed in the VPC.")
   670  		}
   671  	}
   672  
   673  	if v := d.Get("instance_charge_type").(string); v != "" {
   674  		args.InstanceChargeType = common.InstanceChargeType(v)
   675  	}
   676  
   677  	log.Printf("[DEBUG] period is %d", d.Get("period").(int))
   678  	if v := d.Get("period").(int); v != 0 {
   679  		args.Period = v
   680  	} else if args.InstanceChargeType == common.PrePaid {
   681  		return nil, fmt.Errorf("period is required for instance_charge_type is PrePaid")
   682  	}
   683  
   684  	if v := d.Get("user_data").(string); v != "" {
   685  		args.UserData = v
   686  	}
   687  
   688  	return args, nil
   689  }
   690  
   691  func userDataHashSum(user_data string) string {
   692  	// Check whether the user_data is not Base64 encoded.
   693  	// Always calculate hash of base64 decoded value since we
   694  	// check against double-encoding when setting it
   695  	v, base64DecodeError := base64.StdEncoding.DecodeString(user_data)
   696  	if base64DecodeError != nil {
   697  		v = []byte(user_data)
   698  	}
   699  	return string(v)
   700  }