yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/ctyun/instance.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ctyun
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  
    24  	"yunion.io/x/jsonutils"
    25  	"yunion.io/x/log"
    26  	"yunion.io/x/pkg/errors"
    27  	"yunion.io/x/pkg/utils"
    28  
    29  	billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
    30  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    31  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    32  	"yunion.io/x/cloudmux/pkg/multicloud"
    33  	"yunion.io/x/onecloud/pkg/util/billing"
    34  )
    35  
    36  type SInstance struct {
    37  	multicloud.SInstanceBase
    38  	CtyunTags
    39  	multicloud.SBillingBase
    40  
    41  	host  *SHost
    42  	image *SImage
    43  
    44  	vmDetails *InstanceDetails
    45  
    46  	HostID                           string               `json:"hostId"`
    47  	ID                               string               `json:"id"`
    48  	Name                             string               `json:"name"`
    49  	Status                           string               `json:"status"`
    50  	TenantID                         string               `json:"tenant_id"`
    51  	Metadata                         Metadata             `json:"metadata"`
    52  	Image                            Image                `json:"image"`
    53  	Flavor                           FlavorObj            `json:"flavor"`
    54  	Addresses                        map[string][]Address `json:"addresses"`
    55  	UserID                           string               `json:"user_id"`
    56  	Created                          time.Time            `json:"created"`
    57  	DueDate                          *time.Time           `json:"dueDate"`
    58  	SecurityGroups                   []SecurityGroup      `json:"security_groups"`
    59  	OSEXTAZAvailabilityZone          string               `json:"OS-EXT-AZ:availability_zone"`
    60  	OSExtendedVolumesVolumesAttached []Volume             `json:"os-extended-volumes:volumes_attached"`
    61  	MasterOrderId                    string               `json:"masterOrderId"`
    62  }
    63  
    64  type InstanceDetails struct {
    65  	HostID     string      `json:"hostId"`
    66  	Name       string      `json:"name"`
    67  	Status     string      `json:"status"`
    68  	PrivateIPS []PrivateIP `json:"privateIps"`
    69  	PublicIPS  []PublicIP  `json:"publicIps"`
    70  	Volumes    []Volume    `json:"volumes"`
    71  	Created    string      `json:"created"`
    72  	FlavorObj  FlavorObj   `json:"flavorObj"`
    73  }
    74  
    75  type Address struct {
    76  	Addr               string `json:"addr"`
    77  	OSEXTIPSType       string `json:"OS-EXT-IPS:type"`
    78  	Version            int64  `json:"version"`
    79  	OSEXTIPSMACMACAddr string `json:"OS-EXT-IPS-MAC:mac_addr"`
    80  }
    81  
    82  type Image struct {
    83  	Id string `json:"id"`
    84  }
    85  
    86  type SecurityGroup struct {
    87  	Name string `json:"name"`
    88  }
    89  
    90  type FlavorObj struct {
    91  	Name    string `json:"name"`
    92  	CPUNum  int    `json:"cpuNum"`
    93  	MemSize int    `json:"memSize"`
    94  	ID      string `json:"id"`
    95  }
    96  
    97  type PrivateIP struct {
    98  	ID      string `json:"id"`
    99  	Address string `json:"address"`
   100  }
   101  
   102  type PublicP struct {
   103  	ID        string `json:"id"`
   104  	Address   string `json:"address"`
   105  	Bandwidth string `json:"bandwidth"`
   106  }
   107  
   108  func (self *SInstance) GetBillingType() string {
   109  	if self.DueDate != nil {
   110  		return billing_api.BILLING_TYPE_PREPAID
   111  	} else {
   112  		return billing_api.BILLING_TYPE_POSTPAID
   113  	}
   114  }
   115  
   116  func (self *SInstance) GetCreatedAt() time.Time {
   117  	return self.Created
   118  }
   119  
   120  func (self *SInstance) GetExpiredAt() time.Time {
   121  	if self.DueDate == nil {
   122  		return time.Time{}
   123  	}
   124  
   125  	return *self.DueDate
   126  }
   127  
   128  func (self *SInstance) GetId() string {
   129  	return self.ID
   130  }
   131  
   132  func (self *SInstance) GetName() string {
   133  	return self.Name
   134  }
   135  
   136  func (self *SInstance) GetHostname() string {
   137  	return self.Name
   138  }
   139  
   140  func (self *SInstance) GetGlobalId() string {
   141  	return self.GetId()
   142  }
   143  
   144  func (self *SInstance) GetStatus() string {
   145  	switch self.Status {
   146  	case "RUNNING", "ACTIVE":
   147  		return api.VM_RUNNING
   148  	case "RESTARTING", "BUILD", "RESIZE", "VERIFY_RESIZE":
   149  		return api.VM_STARTING
   150  	case "STOPPING", "HARD_REBOOT":
   151  		return api.VM_STOPPING
   152  	case "STOPPED", "SHUTOFF":
   153  		return api.VM_READY
   154  	default:
   155  		return api.VM_UNKNOWN
   156  	}
   157  }
   158  
   159  func (self *SInstance) Refresh() error {
   160  	new, err := self.host.zone.region.GetVMById(self.GetId())
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	new.host = self.host
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	if new.Status == "DELETED" {
   171  		log.Debugf("Instance already terminated.")
   172  		return cloudprovider.ErrNotFound
   173  	}
   174  
   175  	// update details
   176  	detail, err := self.host.zone.region.GetVMDetails(self.GetId())
   177  	if err != nil {
   178  		return errors.Wrap(err, "SInstance.Refresh.GetDetails")
   179  	}
   180  
   181  	self.vmDetails = detail
   182  	return jsonutils.Update(self, new)
   183  }
   184  
   185  func (self *SInstance) IsEmulated() bool {
   186  	return false
   187  }
   188  
   189  func (self *SInstance) GetProjectId() string {
   190  	return ""
   191  }
   192  
   193  func (self *SInstance) GetIHost() cloudprovider.ICloudHost {
   194  	return self.host
   195  }
   196  
   197  // GET http://ctyun-api-url/apiproxy/v3/queryDataDiskByVMId
   198  func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
   199  	details, err := self.GetDetails()
   200  	if err != nil {
   201  		return nil, errors.Wrap(err, "SInstance.GetIDisks.GetDetails")
   202  	}
   203  
   204  	disks := []SDisk{}
   205  	for i := range details.Volumes {
   206  		volume := details.Volumes[i]
   207  		disk, err := self.host.zone.region.GetDisk(volume.ID)
   208  		if err != nil {
   209  			return nil, errors.Wrap(err, "SInstance.GetIDisks.GetDisk")
   210  		}
   211  
   212  		disks = append(disks, *disk)
   213  	}
   214  
   215  	for i := 0; i < len(disks); i += 1 {
   216  		// 将系统盘放到第0个位置
   217  		if disks[i].Bootable == "true" {
   218  			_temp := disks[0]
   219  			disks[0] = disks[i]
   220  			disks[i] = _temp
   221  		}
   222  	}
   223  
   224  	idisks := make([]cloudprovider.ICloudDisk, len(disks))
   225  	for i := range disks {
   226  		disk := disks[i]
   227  		idisks[i] = &disk
   228  	}
   229  
   230  	return idisks, nil
   231  }
   232  
   233  func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
   234  	nics, err := self.host.zone.region.GetNics(self.GetId())
   235  	if err != nil {
   236  		return nil, errors.Wrap(err, "SInstance.GetINics")
   237  	}
   238  
   239  	inics := make([]cloudprovider.ICloudNic, len(nics))
   240  	for i := range nics {
   241  		inics[i] = &nics[i]
   242  	}
   243  
   244  	return inics, nil
   245  }
   246  
   247  // GET http://ctyun-api-urlapiproxy/v3/queryNetworkByVMId
   248  func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
   249  	detail, err := self.GetDetails()
   250  	if err != nil {
   251  		return nil, errors.Wrap(err, "SInstance.GetIEIP.GetDetails")
   252  	}
   253  
   254  	if len(detail.PublicIPS) > 0 {
   255  		return self.host.zone.region.GetEip(detail.PublicIPS[0].ID)
   256  	}
   257  
   258  	return nil, nil
   259  }
   260  
   261  func (self *SInstance) GetDetails() (*InstanceDetails, error) {
   262  	if self.vmDetails != nil {
   263  		return self.vmDetails, nil
   264  	}
   265  
   266  	detail, err := self.host.zone.region.GetVMDetails(self.GetId())
   267  	if err != nil {
   268  		return nil, errors.Wrap(err, "SInstance.GetDetails")
   269  	}
   270  
   271  	self.vmDetails = detail
   272  	return self.vmDetails, nil
   273  }
   274  
   275  func (self *SInstance) GetVcpuCount() int {
   276  	details, err := self.GetDetails()
   277  	if err == nil {
   278  		return details.FlavorObj.CPUNum
   279  	}
   280  
   281  	return self.Flavor.CPUNum
   282  }
   283  
   284  func (self *SInstance) GetVmemSizeMB() int {
   285  	details, err := self.GetDetails()
   286  	if err == nil {
   287  		return details.FlavorObj.MemSize * 1024
   288  	}
   289  
   290  	return self.Flavor.MemSize * 1024
   291  }
   292  
   293  func (self *SInstance) GetBootOrder() string {
   294  	return "dcn"
   295  }
   296  
   297  func (self *SInstance) GetVga() string {
   298  	return "std"
   299  }
   300  
   301  func (self *SInstance) GetVdi() string {
   302  	return "vnc"
   303  }
   304  
   305  func (self *SInstance) GetImage() (*SImage, error) {
   306  	if self.image != nil {
   307  		return self.image, nil
   308  	}
   309  
   310  	image, err := self.host.zone.region.GetImage(self.Image.Id)
   311  	if err != nil {
   312  		return nil, errors.Wrap(err, "SInstance.GetImage")
   313  	}
   314  
   315  	self.image = image
   316  	return self.image, nil
   317  }
   318  
   319  func (self *SInstance) GetOsType() cloudprovider.TOsType {
   320  	image, err := self.GetImage()
   321  	if err != nil {
   322  		log.Errorf("SInstance.Image %s", err)
   323  		return cloudprovider.OsTypeLinux
   324  	}
   325  	return image.GetOsType()
   326  }
   327  
   328  func (self *SInstance) GetFullOsName() string {
   329  	image, err := self.GetImage()
   330  	if err != nil {
   331  		log.Errorf("SInstance.Image %s", err)
   332  		return ""
   333  	}
   334  	return image.GetFullOsName()
   335  }
   336  
   337  func (self *SInstance) GetBios() cloudprovider.TBiosType {
   338  	image, err := self.GetImage()
   339  	if err != nil {
   340  		log.Errorf("SInstance.Image %s", err)
   341  		return cloudprovider.BIOS
   342  	}
   343  	return image.GetBios()
   344  }
   345  
   346  func (self *SInstance) GetOsArch() string {
   347  	image, err := self.GetImage()
   348  	if err != nil {
   349  		log.Errorf("SInstance.Image %s", err)
   350  		return ""
   351  	}
   352  	return image.GetOsArch()
   353  }
   354  
   355  func (self *SInstance) GetOsDist() string {
   356  	image, err := self.GetImage()
   357  	if err != nil {
   358  		log.Errorf("SInstance.Image %s", err)
   359  		return ""
   360  	}
   361  	return image.GetOsDist()
   362  }
   363  
   364  func (self *SInstance) GetOsVersion() string {
   365  	image, err := self.GetImage()
   366  	if err != nil {
   367  		log.Errorf("SInstance.Image %s", err)
   368  		return ""
   369  	}
   370  	return image.GetOsVersion()
   371  }
   372  
   373  func (self *SInstance) GetOsLang() string {
   374  	image, err := self.GetImage()
   375  	if err != nil {
   376  		log.Errorf("SInstance.Image %s", err)
   377  		return ""
   378  	}
   379  	return image.GetOsLang()
   380  }
   381  
   382  func (self *SInstance) GetMachine() string {
   383  	return "pc"
   384  }
   385  
   386  func (self *SInstance) GetInstanceType() string {
   387  	return self.Flavor.ID
   388  }
   389  
   390  func (self *SInstance) GetSecurityGroupIds() ([]string, error) {
   391  	if len(self.SecurityGroups) == 0 {
   392  		return []string{}, nil
   393  	}
   394  
   395  	if len(self.MasterOrderId) > 0 {
   396  		return self.getSecurityGroupIdsByMasterOrderId(self.MasterOrderId)
   397  	}
   398  
   399  	secgroups, err := self.host.zone.region.GetSecurityGroups("")
   400  	if err != nil {
   401  		return nil, errors.Wrap(err, "SInstance.GetSecurityGroupIds.GetSecurityGroups")
   402  	}
   403  
   404  	names := []string{}
   405  	for i := range self.SecurityGroups {
   406  		names = append(names, self.SecurityGroups[i].Name)
   407  	}
   408  
   409  	ids := []string{}
   410  	for i := range secgroups {
   411  		// todo: bugfix 如果安全组重名比较尴尬
   412  		if utils.IsInStringArray(secgroups[i].Name, names) {
   413  			ids = append(ids, secgroups[i].ResSecurityGroupID)
   414  		}
   415  	}
   416  
   417  	return ids, nil
   418  }
   419  
   420  func (self *SInstance) getSecurityGroupIdsByMasterOrderId(orderId string) ([]string, error) {
   421  	orders, err := self.host.zone.region.GetOrder(self.MasterOrderId)
   422  	if err != nil {
   423  		return nil, errors.Wrap(err, "SInstance.GetSecurityGroupIds.GetOrder")
   424  	}
   425  
   426  	if len(orders) == 0 {
   427  		return nil, nil
   428  	}
   429  
   430  	for i := range orders {
   431  		secgroups := orders[i].ResourceConfigMap.SecurityGroups
   432  		if len(secgroups) > 0 {
   433  			ids := []string{}
   434  			for j := range secgroups {
   435  				ids = append(ids, secgroups[j].ID)
   436  			}
   437  
   438  			return ids, nil
   439  		}
   440  	}
   441  
   442  	return nil, nil
   443  }
   444  
   445  func (self *SInstance) AssignSecurityGroup(secgroupId string) error {
   446  	return self.host.zone.region.AssignSecurityGroup(self.GetId(), secgroupId)
   447  }
   448  
   449  func (self *SInstance) SetSecurityGroups(secgroupIds []string) error {
   450  	currentIds, err := self.GetSecurityGroupIds()
   451  	if err != nil {
   452  		return errors.Wrap(err, "GetSecurityGroupIds")
   453  	}
   454  
   455  	adds := []string{}
   456  	for i := range secgroupIds {
   457  		if !utils.IsInStringArray(secgroupIds[i], currentIds) {
   458  			adds = append(adds, secgroupIds[i])
   459  		}
   460  	}
   461  
   462  	for i := range adds {
   463  		err := self.host.zone.region.AssignSecurityGroup(self.GetId(), adds[i])
   464  		if err != nil {
   465  			return errors.Wrap(err, "Instance.SetSecurityGroups")
   466  		}
   467  	}
   468  
   469  	removes := []string{}
   470  	for i := range currentIds {
   471  		if !utils.IsInStringArray(currentIds[i], secgroupIds) {
   472  			removes = append(removes, currentIds[i])
   473  		}
   474  	}
   475  
   476  	for i := range removes {
   477  		err := self.host.zone.region.UnsignSecurityGroup(self.GetId(), removes[i])
   478  		if err != nil {
   479  			return errors.Wrap(err, "Instance.SetSecurityGroups")
   480  		}
   481  	}
   482  
   483  	return nil
   484  }
   485  
   486  func (self *SInstance) GetHypervisor() string {
   487  	return api.HYPERVISOR_CTYUN
   488  }
   489  
   490  func (self *SInstance) StartVM(ctx context.Context) error {
   491  	err := self.host.zone.region.StartVM(self.GetId())
   492  	if err != nil {
   493  		return errors.Wrap(err, "Instance.StartVM")
   494  	}
   495  
   496  	err = cloudprovider.WaitStatus(self, api.VM_RUNNING, 5*time.Second, 300*time.Second)
   497  	if err != nil {
   498  		return errors.Wrap(err, "Instance.StartVM.WaitStatus")
   499  	}
   500  	return nil
   501  }
   502  
   503  func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
   504  	err := self.host.zone.region.StopVM(self.GetId())
   505  	if err != nil {
   506  		return errors.Wrap(err, "Instance.StopVM")
   507  	}
   508  
   509  	err = cloudprovider.WaitStatus(self, api.VM_READY, 5*time.Second, 300*time.Second)
   510  	if err != nil {
   511  		return errors.Wrap(err, "Instance.StopVM.WaitStatus")
   512  	}
   513  	return nil
   514  }
   515  
   516  func (self *SInstance) DeleteVM(ctx context.Context) error {
   517  	err := self.host.zone.region.DeleteVM(self.GetId())
   518  	if err != nil {
   519  		return errors.Wrap(err, "SInstance.DeleteVM")
   520  	}
   521  
   522  	err = cloudprovider.WaitDeleted(self, 10*time.Second, 180*time.Second)
   523  	if err != nil {
   524  		return errors.Wrap(err, "Instance.DeleteVM.WaitDeleted")
   525  	}
   526  	return nil
   527  }
   528  
   529  func (self *SInstance) UpdateVM(ctx context.Context, name string) error {
   530  	return cloudprovider.ErrNotSupported
   531  }
   532  
   533  func (self *SInstance) UpdateUserData(userData string) error {
   534  	return cloudprovider.ErrNotSupported
   535  }
   536  
   537  func (self *SInstance) RebuildRoot(ctx context.Context, config *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
   538  	currentImage, err := self.GetImage()
   539  	if err != nil {
   540  		return "", errors.Wrap(err, "Instance.RebuildRoot")
   541  	}
   542  
   543  	publicKeyName := ""
   544  	if len(config.PublicKey) > 0 {
   545  		publicKeyName, err = self.host.zone.region.syncKeypair(config.PublicKey)
   546  		if err != nil {
   547  			return "", errors.Wrap(err, "Instance.RebuildRoot.syncKeypair")
   548  		}
   549  	}
   550  
   551  	jobId := ""
   552  	if currentImage.GetId() != config.ImageId {
   553  		jobId, err = self.host.zone.region.SwitchVMOs(self.GetId(), config.Password, publicKeyName, config.ImageId)
   554  		if err != nil {
   555  			return "", errors.Wrap(err, "SInstance.RebuildRoot.SwitchVMOs")
   556  		}
   557  	} else {
   558  		jobId, err = self.host.zone.region.RebuildVM(self.GetId(), config.Password, publicKeyName)
   559  		if err != nil {
   560  			return "", errors.Wrap(err, "SInstance.RebuildRoot.RebuildVM")
   561  		}
   562  	}
   563  
   564  	err = cloudprovider.Wait(10*time.Second, 1800*time.Second, func() (b bool, err error) {
   565  		statusJson, err := self.host.zone.region.GetJob(jobId)
   566  		if err != nil {
   567  			if strings.Contains(err.Error(), "job fail") {
   568  				return false, err
   569  			}
   570  
   571  			return false, nil
   572  		}
   573  
   574  		if status, _ := statusJson.GetString("status"); status == "SUCCESS" {
   575  			return true, nil
   576  		} else if status == "FAILED" {
   577  			return false, fmt.Errorf("RebuildRoot job %s failed", jobId)
   578  		} else {
   579  			return false, nil
   580  		}
   581  	})
   582  	if err != nil {
   583  		return "", errors.Wrap(err, "Instance.RebuildRoot.Wait")
   584  	}
   585  
   586  	err = self.Refresh()
   587  	if err != nil {
   588  		return "", err
   589  	}
   590  
   591  	idisks, err := self.GetIDisks()
   592  	if err != nil {
   593  		return "", err
   594  	}
   595  
   596  	if len(idisks) == 0 {
   597  		return "", fmt.Errorf("server %s has no volume attached.", self.GetId())
   598  	}
   599  
   600  	return idisks[0].GetId(), nil
   601  }
   602  
   603  func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error {
   604  	if len(password) == 0 {
   605  		return cloudprovider.ErrNotSupported
   606  	}
   607  
   608  	// 只支持重置密码
   609  	return self.host.zone.region.ResetVMPassword(self.GetId(), password)
   610  }
   611  
   612  func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
   613  	jobId, err := self.host.zone.region.ChangeVMConfig(self.GetId(), config.InstanceType)
   614  	if err != nil {
   615  		return errors.Wrap(err, "Instance.ChangeConfig")
   616  	}
   617  
   618  	err = cloudprovider.Wait(10*time.Second, 1800*time.Second, func() (b bool, err error) {
   619  		statusJson, err := self.host.zone.region.GetJob(jobId)
   620  		if err != nil {
   621  			if strings.Contains(err.Error(), "job fail") {
   622  				return false, err
   623  			}
   624  
   625  			return false, nil
   626  		}
   627  
   628  		if status, _ := statusJson.GetString("status"); status == "SUCCESS" {
   629  			return true, nil
   630  		} else if status == "FAILED" {
   631  			return false, fmt.Errorf("ChangeConfig job %s failed", jobId)
   632  		} else {
   633  			return false, nil
   634  		}
   635  	})
   636  	if err != nil {
   637  		return errors.Wrap(err, "Instance.ChangeConfig.Wait")
   638  	}
   639  
   640  	return nil
   641  }
   642  
   643  // http://ctyun-api-url/apiproxy/v3/queryVncUrl
   644  func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
   645  	url, err := self.host.zone.region.GetInstanceVNCUrl(self.GetId())
   646  	if err != nil {
   647  		return nil, err
   648  	}
   649  	ret := &cloudprovider.ServerVncOutput{
   650  		Url:        url,
   651  		Protocol:   "ctyun",
   652  		InstanceId: self.GetId(),
   653  		Hypervisor: api.HYPERVISOR_CTYUN,
   654  	}
   655  	return ret, nil
   656  }
   657  
   658  func (self *SInstance) NextDeviceName() (string, error) {
   659  	details, err := self.GetDetails()
   660  	if err != nil {
   661  		return "", errors.Wrap(err, "SInstance.NextDeviceName.GetDetails")
   662  	}
   663  
   664  	disks := []*SDisk{}
   665  	for i := range details.Volumes {
   666  		disk, err := self.host.zone.region.GetDisk(details.Volumes[i].ID)
   667  		if err != nil {
   668  			return "", errors.Wrap(err, "SInstance.NextDeviceName.GetDisk")
   669  		}
   670  
   671  		disks = append(disks, disk)
   672  	}
   673  
   674  	prefix := "s"
   675  	if len(disks) > 0 && strings.Contains(disks[0].GetMountpoint(), "/vd") {
   676  		prefix = "v"
   677  	}
   678  
   679  	currents := []string{}
   680  	for _, disk := range disks {
   681  		currents = append(currents, strings.ToLower(disk.GetMountpoint()))
   682  	}
   683  
   684  	for i := 0; i < 25; i++ {
   685  		device := fmt.Sprintf("/dev/%sd%s", prefix, string([]byte{byte(98 + i)}))
   686  		if ok, _ := utils.InStringArray(device, currents); !ok {
   687  			return device, nil
   688  		}
   689  	}
   690  
   691  	return "", fmt.Errorf("disk devicename out of index, current deivces: %s", currents)
   692  }
   693  
   694  func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
   695  	device, err := self.NextDeviceName()
   696  	if err != nil {
   697  		return errors.Wrap(err, "Instance.AttachDisk.NextDeviceName")
   698  	}
   699  
   700  	_, err = self.host.zone.region.AttachDisk(self.GetId(), diskId, device)
   701  	if err != nil {
   702  		return errors.Wrap(err, "Instance.AttachDisk")
   703  	}
   704  
   705  	disk, err := self.host.zone.region.GetDisk(diskId)
   706  	if err != nil {
   707  		return errors.Wrap(err, "AttachDisk.GetDisk")
   708  	}
   709  
   710  	err = cloudprovider.WaitStatusWithDelay(disk, api.DISK_READY, 10*time.Second, 5*time.Second, 180*time.Second)
   711  	if err != nil {
   712  		return errors.Wrap(err, "Instance.DetachDisk.WaitStatusWithDelay")
   713  	}
   714  
   715  	if disk.Status != "in-use" {
   716  		return errors.Wrap(fmt.Errorf("disk status %s", disk.Status), "Instance.DetachDisk.Status")
   717  	}
   718  
   719  	return nil
   720  }
   721  
   722  func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
   723  	disk, err := self.host.zone.region.GetDisk(diskId)
   724  	if err != nil {
   725  		return errors.Wrap(err, "DetachDisk.Wait")
   726  	}
   727  
   728  	if len(disk.Attachments) == 0 {
   729  		return errors.Wrap(err, "Instance.DetachDisk")
   730  	}
   731  
   732  	_, err = self.host.zone.region.DetachDisk(self.GetId(), diskId, disk.Attachments[0].Device)
   733  	if err != nil {
   734  		return errors.Wrap(err, "Instance.DetachDisk")
   735  	}
   736  
   737  	err = cloudprovider.WaitStatusWithDelay(disk, api.DISK_READY, 10*time.Second, 5*time.Second, 180*time.Second)
   738  	if err != nil {
   739  		return errors.Wrap(err, "Instance.DetachDisk.WaitStatusWithDelay")
   740  	}
   741  
   742  	if disk.Status != "available" {
   743  		return errors.Wrap(fmt.Errorf("disk status %s", disk.Status), "Instance.DetachDisk.Status")
   744  	}
   745  
   746  	return nil
   747  }
   748  
   749  func (self *SInstance) Renew(bc billing.SBillingCycle) error {
   750  	_, err := self.host.zone.region.RenewVM(self.GetId(), &bc)
   751  	if err != nil {
   752  		return errors.Wrap(err, "Instance.Renew.RenewVM")
   753  	}
   754  
   755  	return nil
   756  }
   757  
   758  func (self *SInstance) GetError() error {
   759  	return nil
   760  }
   761  
   762  type SDiskDetails struct {
   763  	HostID     string      `json:"hostId"`
   764  	Name       string      `json:"name"`
   765  	Status     string      `json:"status"`
   766  	PrivateIPS []PrivateIP `json:"privateIps"`
   767  	PublicIPS  []PublicIP  `json:"publicIps"`
   768  	Volumes    []Volume    `json:"volumes"`
   769  	Created    string      `json:"created"`
   770  	FlavorObj  FlavorObj   `json:"flavorObj"`
   771  }
   772  
   773  type PublicIP struct {
   774  	ID        string `json:"id"`
   775  	Address   string `json:"address"`
   776  	Bandwidth string `json:"bandwidth"`
   777  }
   778  
   779  type Volume struct {
   780  	ID       string `json:"id"`
   781  	Status   string `json:"status"`
   782  	Type     string `json:"type"`
   783  	Size     string `json:"size"`
   784  	Name     string `json:"name"`
   785  	Bootable bool   `json:"bootable"`
   786  }
   787  
   788  func (self *SRegion) GetVMDetails(vmId string) (*InstanceDetails, error) {
   789  	params := map[string]string{
   790  		"regionId": self.GetId(),
   791  		"vmId":     vmId,
   792  	}
   793  
   794  	resp, err := self.client.DoGet("/apiproxy/v3/ondemand/queryVMDetail", params)
   795  	if err != nil {
   796  		return nil, errors.Wrap(err, "SRegion.GetVMDetails.DoGet")
   797  	}
   798  
   799  	details := &InstanceDetails{}
   800  	err = resp.Unmarshal(details, "returnObj")
   801  	if err != nil {
   802  		return nil, errors.Wrap(err, "SRegion.GetVMDetails.Unmarshal")
   803  	}
   804  
   805  	return details, nil
   806  }
   807  
   808  type SVncInfo struct {
   809  	Type string `json:"type"`
   810  	URL  string `json:"url"`
   811  }
   812  
   813  func (self *SRegion) GetInstanceVNCUrl(vmId string) (string, error) {
   814  	params := map[string]string{
   815  		"regionId": self.GetId(),
   816  		"vmId":     vmId,
   817  	}
   818  
   819  	resp, err := self.client.DoGet("/apiproxy/v3/queryVncUrl", params)
   820  	if err != nil {
   821  		return "", errors.Wrap(err, "")
   822  	}
   823  
   824  	ret := SVncInfo{}
   825  	err = resp.Unmarshal(&ret, "returnObj", "console")
   826  	if err != nil {
   827  		return "", errors.Wrap(err, "")
   828  	}
   829  
   830  	return ret.URL, nil
   831  }
   832  
   833  /*
   834  创建主机接口目前没有绑定密钥的参数选项,不支持绑定密码。
   835  但是重装系统接口支持绑定密钥
   836  */
   837  func (self *SRegion) CreateInstance(zoneId, name, imageId, osType, flavorRef, vpcid, subnetId, secGroupId, adminPass, volumetype string, volumeSize int, dataDisks []cloudprovider.SDiskInfo) (string, error) {
   838  	rootParams := jsonutils.NewDict()
   839  	rootParams.Set("volumetype", jsonutils.NewString(volumetype))
   840  	if volumeSize > 0 {
   841  		rootParams.Set("size", jsonutils.NewInt(int64(volumeSize)))
   842  	}
   843  
   844  	nicParams := jsonutils.NewArray()
   845  	nicParam := jsonutils.NewDict()
   846  	nicParam.Set("subnet_id", jsonutils.NewString(subnetId))
   847  	nicParams.Add(nicParam)
   848  
   849  	secgroupParams := jsonutils.NewArray()
   850  	secgroupParam := jsonutils.NewDict()
   851  	secgroupParam.Set("id", jsonutils.NewString(secGroupId))
   852  	secgroupParams.Add(secgroupParam)
   853  
   854  	extParams := jsonutils.NewDict()
   855  	extParams.Set("regionID", jsonutils.NewString(self.GetId()))
   856  
   857  	serverParams := jsonutils.NewDict()
   858  	serverParams.Set("availability_zone", jsonutils.NewString(zoneId))
   859  	serverParams.Set("name", jsonutils.NewString(name))
   860  	serverParams.Set("imageRef", jsonutils.NewString(imageId))
   861  	serverParams.Set("root_volume", rootParams)
   862  	serverParams.Set("flavorRef", jsonutils.NewString(flavorRef))
   863  	serverParams.Set("osType", jsonutils.NewString(osType))
   864  	serverParams.Set("vpcid", jsonutils.NewString(vpcid))
   865  	serverParams.Set("security_groups", secgroupParams)
   866  	serverParams.Set("nics", nicParams)
   867  	serverParams.Set("adminPass", jsonutils.NewString(adminPass))
   868  	serverParams.Set("count", jsonutils.NewString("1"))
   869  	serverParams.Set("extendparam", extParams)
   870  
   871  	if dataDisks != nil && len(dataDisks) > 0 {
   872  		dataDisksParams := jsonutils.NewArray()
   873  		for i := range dataDisks {
   874  			dataDiskParams := jsonutils.NewDict()
   875  			dataDiskParams.Set("volumetype", jsonutils.NewString(dataDisks[i].StorageType))
   876  			dataDiskParams.Set("size", jsonutils.NewInt(int64(dataDisks[i].SizeGB)))
   877  			dataDisksParams.Add(dataDiskParams)
   878  		}
   879  
   880  		serverParams.Set("data_volumes", dataDisksParams)
   881  	}
   882  
   883  	vmParams := jsonutils.NewDict()
   884  	vmParams.Set("server", serverParams)
   885  
   886  	params := map[string]jsonutils.JSONObject{
   887  		"createVMInfo": vmParams,
   888  	}
   889  
   890  	resp, err := self.client.DoPost("/apiproxy/v3/ondemand/createVM", params)
   891  	if err != nil {
   892  		return "", errors.Wrap(err, "SRegion.CreateInstance.DoPost")
   893  	}
   894  
   895  	var ok bool
   896  	err = resp.Unmarshal(&ok, "returnObj", "status")
   897  	if !ok {
   898  		msg, _ := resp.GetString("returnObj", "message")
   899  		return "", errors.Wrap(fmt.Errorf(msg), "SRegion.CreateInstance.JobFailed")
   900  	}
   901  
   902  	var jobId string
   903  	err = resp.Unmarshal(&jobId, "returnObj", "data")
   904  	if err != nil {
   905  		return "", errors.Wrap(err, "SRegion.CreateInstance.Unmarshal")
   906  	}
   907  
   908  	return jobId, nil
   909  }
   910  
   911  // vm & nic job
   912  func (self *SRegion) GetJob(jobId string) (jsonutils.JSONObject, error) {
   913  	params := map[string]string{
   914  		"regionId": self.GetId(),
   915  		"jobId":    jobId,
   916  	}
   917  
   918  	resp, err := self.client.DoGet("/apiproxy/v3/queryJobStatus", params)
   919  	if err != nil {
   920  		return nil, errors.Wrap(err, "SRegion.GetJob.DoGet")
   921  	}
   922  
   923  	ret := jsonutils.NewDict()
   924  	err = resp.Unmarshal(&ret, "returnObj")
   925  	if err != nil {
   926  		return nil, errors.Wrap(err, "SRegion.GetJob.Unmarshal")
   927  	}
   928  
   929  	return ret, nil
   930  }
   931  
   932  // 查询云硬盘备份JOB状态信息
   933  func (self *SRegion) GetVbsJob(jobId string) (jsonutils.JSONObject, error) {
   934  	params := map[string]string{
   935  		"regionId": self.GetId(),
   936  		"jobId":    jobId,
   937  	}
   938  
   939  	resp, err := self.client.DoGet("/apiproxy/v3/ondemand/queryVbsJob", params)
   940  	if err != nil {
   941  		return nil, errors.Wrap(err, "SRegion.GetVbsJob.DoGet")
   942  	}
   943  
   944  	ret := jsonutils.NewDict()
   945  	err = resp.Unmarshal(&ret, "returnObj")
   946  	if err != nil {
   947  		return nil, errors.Wrap(err, "SRegion.GetVbsJob.Unmarshal")
   948  	}
   949  
   950  	return ret, nil
   951  }
   952  
   953  // 查询云硬盘JOB状态信息
   954  func (self *SRegion) GetVolumeJob(jobId string) (jsonutils.JSONObject, error) {
   955  	params := map[string]string{
   956  		"regionId": self.GetId(),
   957  		"jobId":    jobId,
   958  	}
   959  
   960  	resp, err := self.client.DoGet("/apiproxy/v3/queryVolumeJob", params)
   961  	if err != nil {
   962  		return nil, errors.Wrap(err, "SRegion.GetVolumeJob.DoGet")
   963  	}
   964  
   965  	ret := jsonutils.NewDict()
   966  	err = resp.Unmarshal(&ret, "returnObj")
   967  	if err != nil {
   968  		return nil, errors.Wrap(err, "SRegion.GetVolumeJob.Unmarshal")
   969  	}
   970  
   971  	return ret, nil
   972  }
   973  
   974  // POST http://ctyun-api-url/apiproxy/v3/addSecurityGroup 绑定安全组
   975  func (self *SRegion) AssignSecurityGroup(vmId, securityGroupRuleId string) error {
   976  	securityParams := jsonutils.NewDict()
   977  	securityParams.Set("regionId", jsonutils.NewString(self.GetId()))
   978  	securityParams.Set("vmId", jsonutils.NewString(vmId))
   979  	securityParams.Set("securityGroupRuleId", jsonutils.NewString(securityGroupRuleId))
   980  
   981  	params := map[string]jsonutils.JSONObject{
   982  		"securityGroup": securityParams,
   983  	}
   984  
   985  	_, err := self.client.DoPost("/apiproxy/v3/addSecurityGroup", params)
   986  	if err != nil {
   987  		return errors.Wrap(err, "SRegion.AssignSecurityGroup.DoPost")
   988  	}
   989  
   990  	return nil
   991  }
   992  
   993  // POST http://ctyun-api-url/apiproxy/v3/removeSecurityGroup 解绑安全组
   994  func (self *SRegion) UnsignSecurityGroup(vmId, securityGroupRuleId string) error {
   995  	securityParams := jsonutils.NewDict()
   996  	securityParams.Set("regionId", jsonutils.NewString(self.GetId()))
   997  	securityParams.Set("vmId", jsonutils.NewString(vmId))
   998  	securityParams.Set("securityGroupRuleId", jsonutils.NewString(securityGroupRuleId))
   999  
  1000  	params := map[string]jsonutils.JSONObject{
  1001  		"securityGroup": securityParams,
  1002  	}
  1003  
  1004  	_, err := self.client.DoPost("/apiproxy/v3/removeSecurityGroup", params)
  1005  	if err != nil {
  1006  		return errors.Wrap(err, "SRegion.UnsignSecurityGroup.DoPost")
  1007  	}
  1008  
  1009  	return nil
  1010  }
  1011  
  1012  func (self *SRegion) StartVM(vmId string) error {
  1013  	params := map[string]jsonutils.JSONObject{
  1014  		"regionId": jsonutils.NewString(self.GetId()),
  1015  		"vmId":     jsonutils.NewString(vmId),
  1016  	}
  1017  
  1018  	_, err := self.client.DoPost("/apiproxy/v3/ondemand/startVM", params)
  1019  	if err != nil {
  1020  		return errors.Wrap(err, "SRegion.StartVm.DoPost")
  1021  	}
  1022  
  1023  	return nil
  1024  }
  1025  
  1026  func (self *SRegion) StopVM(vmId string) error {
  1027  	params := map[string]jsonutils.JSONObject{
  1028  		"regionId": jsonutils.NewString(self.GetId()),
  1029  		"vmId":     jsonutils.NewString(vmId),
  1030  	}
  1031  
  1032  	_, err := self.client.DoPost("/apiproxy/v3/ondemand/stopVM", params)
  1033  	if err != nil {
  1034  		return errors.Wrap(err, "SRegion.StopVM.DoPost")
  1035  	}
  1036  
  1037  	return nil
  1038  }
  1039  
  1040  func (self *SRegion) DeleteVM(vmId string) error {
  1041  	params := map[string]jsonutils.JSONObject{
  1042  		"regionId": jsonutils.NewString(self.GetId()),
  1043  		"vmId":     jsonutils.NewString(vmId),
  1044  	}
  1045  
  1046  	_, err := self.client.DoPost("/apiproxy/v3/ondemand/deleteVM", params)
  1047  	if err != nil {
  1048  		return errors.Wrap(err, "SRegion.DeleteVM.DoPost")
  1049  	}
  1050  
  1051  	return nil
  1052  }
  1053  
  1054  func (self *SRegion) RestartVM(vmId string) error {
  1055  	params := map[string]jsonutils.JSONObject{
  1056  		"regionId": jsonutils.NewString(self.GetId()),
  1057  		"vmId":     jsonutils.NewString(vmId),
  1058  		"type":     jsonutils.NewString("SOFT"),
  1059  	}
  1060  
  1061  	_, err := self.client.DoPost("/apiproxy/v3/ondemand/restartVM", params)
  1062  	if err != nil {
  1063  		return errors.Wrap(err, "SRegion.RestartVM.DoPost")
  1064  	}
  1065  
  1066  	return nil
  1067  }
  1068  
  1069  func (self *SRegion) SwitchVMOs(vmId, adminPass, keyName, imageRef string) (string, error) {
  1070  	params := map[string]jsonutils.JSONObject{
  1071  		"regionId": jsonutils.NewString(self.GetId()),
  1072  		"vmId":     jsonutils.NewString(vmId),
  1073  		"imageRef": jsonutils.NewString(imageRef),
  1074  	}
  1075  
  1076  	if len(keyName) > 0 {
  1077  		params["keyName"] = jsonutils.NewString(keyName)
  1078  	} else if len(adminPass) > 0 {
  1079  		params["adminPass"] = jsonutils.NewString(adminPass)
  1080  	} else {
  1081  		return "", errors.Wrap(fmt.Errorf("require public key or password"), "SRegion.SwitchVMOs")
  1082  	}
  1083  
  1084  	resp, err := self.client.DoPost("/apiproxy/v3/ondemand/switchSys", params)
  1085  	if err != nil {
  1086  		return "", errors.Wrap(err, "SRegion.SwitchVMOs.DoPost")
  1087  	}
  1088  
  1089  	var ok bool
  1090  	err = resp.Unmarshal(&ok, "returnObj", "status")
  1091  	if !ok {
  1092  		msg, _ := resp.GetString("returnObj", "message")
  1093  		return "", errors.Wrap(fmt.Errorf(msg), "SRegion.SwitchVMOs.JobFailed")
  1094  	}
  1095  
  1096  	var jobId string
  1097  	err = resp.Unmarshal(&jobId, "returnObj", "data")
  1098  	if err != nil {
  1099  		return "", errors.Wrap(err, "SRegion.SwitchVMOs.Unmarshal")
  1100  	}
  1101  
  1102  	return jobId, nil
  1103  }
  1104  
  1105  func (self *SRegion) RebuildVM(vmId, adminPass, keyName string) (string, error) {
  1106  	params := map[string]jsonutils.JSONObject{
  1107  		"regionId": jsonutils.NewString(self.GetId()),
  1108  		"vmId":     jsonutils.NewString(vmId),
  1109  	}
  1110  
  1111  	if len(keyName) > 0 {
  1112  		params["keyName"] = jsonutils.NewString(keyName)
  1113  	} else if len(adminPass) > 0 {
  1114  		params["adminPass"] = jsonutils.NewString(adminPass)
  1115  	} else {
  1116  		return "", errors.Wrap(fmt.Errorf("require public key or password"), "SRegion.RebuildVM")
  1117  	}
  1118  
  1119  	resp, err := self.client.DoPost("/apiproxy/v3/ondemand/reInstallSys", params)
  1120  	if err != nil {
  1121  		return "", errors.Wrap(err, "SRegion.RebuildVM.DoPost")
  1122  	}
  1123  
  1124  	var ok bool
  1125  	err = resp.Unmarshal(&ok, "returnObj", "status")
  1126  	if !ok {
  1127  		msg, _ := resp.GetString("returnObj", "message")
  1128  		return "", errors.Wrap(fmt.Errorf(msg), "SRegion.RebuildVM.JobFailed")
  1129  	}
  1130  
  1131  	var jobId string
  1132  	err = resp.Unmarshal(&jobId, "returnObj", "data")
  1133  	if err != nil {
  1134  		return "", errors.Wrap(err, "SRegion.RebuildVM.Unmarshal")
  1135  	}
  1136  
  1137  	return jobId, nil
  1138  }
  1139  
  1140  func (self *SRegion) AttachDisk(vmId, volumeId, device string) (string, error) {
  1141  	params := map[string]jsonutils.JSONObject{
  1142  		"regionId": jsonutils.NewString(self.GetId()),
  1143  		"volumeId": jsonutils.NewString(volumeId),
  1144  		"vmId":     jsonutils.NewString(vmId),
  1145  		"device":   jsonutils.NewString(device),
  1146  	}
  1147  
  1148  	resp, err := self.client.DoPost("/apiproxy/v3/ondemand/attachVolume", params)
  1149  	if err != nil {
  1150  		return "", errors.Wrap(err, "SRegion.AttachDisk.DoPost")
  1151  	}
  1152  
  1153  	var ok bool
  1154  	err = resp.Unmarshal(&ok, "returnObj", "status")
  1155  	if !ok {
  1156  		msg, _ := resp.GetString("returnObj", "message")
  1157  		return "", errors.Wrap(fmt.Errorf(msg), "SRegion.AttachDisk.JobFailed")
  1158  	}
  1159  
  1160  	var jobId string
  1161  	err = resp.Unmarshal(&jobId, "returnObj", "data")
  1162  	if err != nil {
  1163  		return "", errors.Wrap(err, "SRegion.AttachDisk.Unmarshal")
  1164  	}
  1165  
  1166  	return jobId, nil
  1167  }
  1168  
  1169  func (self *SRegion) DetachDisk(vmId, volumeId, device string) (string, error) {
  1170  	params := map[string]jsonutils.JSONObject{
  1171  		"regionId": jsonutils.NewString(self.GetId()),
  1172  		"volumeId": jsonutils.NewString(volumeId),
  1173  		"vmId":     jsonutils.NewString(vmId),
  1174  		"device":   jsonutils.NewString(device),
  1175  	}
  1176  
  1177  	resp, err := self.client.DoPost("/apiproxy/v3/ondemand/uninstallVolume", params)
  1178  	if err != nil {
  1179  		return "", errors.Wrap(err, "SRegion.DetachDisk.DoPost")
  1180  	}
  1181  
  1182  	var ok bool
  1183  	err = resp.Unmarshal(&ok, "returnObj", "status")
  1184  	if !ok {
  1185  		msg, _ := resp.GetString("returnObj", "message")
  1186  		return "", errors.Wrap(fmt.Errorf(msg), "SRegion.DetachDisk.JobFailed")
  1187  	}
  1188  
  1189  	var jobId string
  1190  	err = resp.Unmarshal(&jobId, "returnObj", "data")
  1191  	if err != nil {
  1192  		return "", errors.Wrap(err, "SRegion.DetachDisk.Unmarshal")
  1193  	}
  1194  
  1195  	return jobId, nil
  1196  }
  1197  
  1198  func (self *SRegion) ChangeVMConfig(vmId, flavorId string) (string, error) {
  1199  	params := map[string]jsonutils.JSONObject{
  1200  		"regionId": jsonutils.NewString(self.GetId()),
  1201  		"vmId":     jsonutils.NewString(vmId),
  1202  		"flavorId": jsonutils.NewString(flavorId),
  1203  	}
  1204  
  1205  	resp, err := self.client.DoPost("/apiproxy/v3/ondemand/upgradeVM", params)
  1206  	if err != nil {
  1207  		return "", errors.Wrap(err, "SRegion.ChangeVMConfig.DoPost")
  1208  	}
  1209  
  1210  	var ok bool
  1211  	err = resp.Unmarshal(&ok, "returnObj", "status")
  1212  	if !ok {
  1213  		msg, _ := resp.GetString("returnObj", "message")
  1214  		return "", errors.Wrap(fmt.Errorf(msg), "SRegion.ChangeVMConfig.JobFailed")
  1215  	}
  1216  
  1217  	var jobId string
  1218  	err = resp.Unmarshal(&jobId, "returnObj", "data")
  1219  	if err != nil {
  1220  		return "", errors.Wrap(err, "SRegion.ChangeVMConfig.Unmarshal")
  1221  	}
  1222  
  1223  	return jobId, nil
  1224  }
  1225  
  1226  // POST http://ctyun-api-url/apiproxy/v3/order/placeRenewOrder 续订
  1227  func (self *SRegion) RenewVM(vmId string, bc *billing.SBillingCycle) ([]string, error) {
  1228  	if bc == nil {
  1229  		return nil, errors.Wrap(fmt.Errorf("SBillingCycle is nil"), "Region.RenewVM")
  1230  	}
  1231  
  1232  	resourcePackage := jsonutils.NewDict()
  1233  	month := bc.GetMonths()
  1234  	switch {
  1235  	case month <= 11:
  1236  		resourcePackage.Set("cycleCount", jsonutils.NewString(strconv.Itoa(month)))
  1237  		resourcePackage.Set("cycleType", jsonutils.NewString("3"))
  1238  	case month == 12:
  1239  		resourcePackage.Set("cycleCount", jsonutils.NewString("1"))
  1240  		resourcePackage.Set("cycleType", jsonutils.NewString("5"))
  1241  	case month == 24:
  1242  		resourcePackage.Set("cycleCount", jsonutils.NewString("1"))
  1243  		resourcePackage.Set("cycleType", jsonutils.NewString("6"))
  1244  	case month == 36:
  1245  		resourcePackage.Set("cycleCount", jsonutils.NewString("1"))
  1246  		resourcePackage.Set("cycleType", jsonutils.NewString("7"))
  1247  	default:
  1248  		return nil, errors.Wrap(fmt.Errorf("unsupported month duration %d. expected 1~11, 12, 24, 36", month), "Region.RenewVM")
  1249  	}
  1250  
  1251  	vmIds := jsonutils.NewArray()
  1252  	vmIds.Add(jsonutils.NewString(vmId))
  1253  	resourcePackage.Set("resourceIds", vmIds)
  1254  
  1255  	params := map[string]jsonutils.JSONObject{
  1256  		"resourceDetailJson": resourcePackage,
  1257  	}
  1258  
  1259  	resp, err := self.client.DoPost("/apiproxy/v3/order/placeRenewOrder", params)
  1260  	if err != nil {
  1261  		return nil, errors.Wrap(err, "SRegion.RenewVM.DoPost")
  1262  	}
  1263  
  1264  	var ok bool
  1265  	err = resp.Unmarshal(&ok, "returnObj", "submitted")
  1266  	if !ok {
  1267  		msg, _ := resp.GetString("returnObj", "message")
  1268  		return nil, errors.Wrap(fmt.Errorf(msg), "SRegion.RenewVM.JobFailed")
  1269  	}
  1270  
  1271  	type OrderPlacedEventsElement struct {
  1272  		ErrorMessage string `json:"errorMessage"`
  1273  		Submitted    bool   `json:"submitted"`
  1274  		NewOrderID   string `json:"newOrderId"`
  1275  		NewOrderNo   string `json:"newOrderNo"`
  1276  		TotalPrice   int64  `json:"totalPrice"`
  1277  	}
  1278  
  1279  	orders := []OrderPlacedEventsElement{}
  1280  	err = resp.Unmarshal(&orders, "returnObj", "orderPlacedEvents")
  1281  	if err != nil {
  1282  		return nil, errors.Wrap(err, "SRegion.RenewVM.Unmarshal")
  1283  	}
  1284  
  1285  	orderIds := []string{}
  1286  	for i := range orders {
  1287  		orderIds = append(orderIds, orders[i].NewOrderID)
  1288  	}
  1289  
  1290  	return orderIds, nil
  1291  }
  1292  
  1293  func (self *SRegion) ResetVMPassword(vmId, password string) error {
  1294  	params := map[string]jsonutils.JSONObject{
  1295  		"regionId": jsonutils.NewString(self.GetId()),
  1296  		"vmId":     jsonutils.NewString(vmId),
  1297  		"password": jsonutils.NewString(password),
  1298  	}
  1299  
  1300  	_, err := self.client.DoPost("/apiproxy/v3/resetVmPassword", params)
  1301  	if err != nil {
  1302  		return errors.Wrap(err, "SRegion.ResetVMPassword.DoPost")
  1303  	}
  1304  
  1305  	return nil
  1306  }