yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/zstack/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 zstack
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net/url"
    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  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    30  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    31  	"yunion.io/x/cloudmux/pkg/multicloud"
    32  	"yunion.io/x/onecloud/pkg/util/billing"
    33  	"yunion.io/x/onecloud/pkg/util/imagetools"
    34  	"yunion.io/x/onecloud/pkg/util/version"
    35  )
    36  
    37  type SInstanceCdrome struct {
    38  }
    39  
    40  type SInstance struct {
    41  	multicloud.SInstanceBase
    42  	ZStackTags
    43  	host *SHost
    44  
    45  	osInfo *imagetools.ImageInfo
    46  
    47  	ZStackBasic
    48  	ZoneUUID             string `json:"zoneUuid"`
    49  	ClusterUUID          string `json:"clusterUuid"`
    50  	HostUUID             string `json:"hostUuid"`
    51  	LastHostUUID         string `json:"lastHostUuid"`
    52  	RootVolumeUUID       string `json:"rootVolumeUuid"`
    53  	Platform             string `json:"platform"`
    54  	InstanceOfferingUUID string `json:"instanceOfferingUuid"`
    55  
    56  	DefaultL3NetworkUUID string            `json:"defaultL3NetworkUuid"`
    57  	Type                 string            `json:"type"`
    58  	HypervisorType       string            `json:"hypervisorType"`
    59  	MemorySize           int               `json:"memorySize"`
    60  	CPUNum               int               `json:"cpuNum"`
    61  	CPUSpeed             int               `json:"cpuSpeed"`
    62  	State                string            `json:"state"`
    63  	InternalID           string            `json:"internalId"`
    64  	VMNics               []SInstanceNic    `json:"vmNics"`
    65  	AllVolumes           []SDisk           `json:"allVolumes"`
    66  	VMCdRoms             []SInstanceCdrome `json:"vmCdRoms"`
    67  	ZStackTime
    68  }
    69  
    70  func (region *SRegion) GetInstance(instanceId string) (*SInstance, error) {
    71  	instance := &SInstance{}
    72  	err := region.client.getResource("vm-instances", instanceId, instance)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	if instance.State == "Destroyed" {
    77  		return nil, cloudprovider.ErrNotFound
    78  	}
    79  	return instance, nil
    80  }
    81  
    82  func (region *SRegion) GetInstances(hostId string, instanceId string, nicId string) ([]SInstance, error) {
    83  	instance := []SInstance{}
    84  	params := url.Values{}
    85  	params.Add("q", "type=UserVm")
    86  	params.Add("q", "state!=Destroyed")
    87  	if len(hostId) > 0 {
    88  		params.Add("q", "lastHostUuid="+hostId)
    89  	}
    90  	if len(instanceId) > 0 {
    91  		params.Add("q", "uuid="+instanceId)
    92  	}
    93  	if len(nicId) > 0 {
    94  		params.Add("q", "vmNics.uuid="+nicId)
    95  	}
    96  	if SkipEsxi {
    97  		params.Add("q", "hypervisorType!=ESX")
    98  	}
    99  	return instance, region.client.listAll("vm-instances", params, &instance)
   100  }
   101  
   102  func (instance *SInstance) GetSecurityGroupIds() ([]string, error) {
   103  	ids := []string{}
   104  	secgroups, err := instance.host.zone.region.GetSecurityGroups("", instance.UUID, "")
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	for _, secgroup := range secgroups {
   109  		ids = append(ids, secgroup.UUID)
   110  	}
   111  	return ids, nil
   112  }
   113  
   114  func (instance *SInstance) GetIHost() cloudprovider.ICloudHost {
   115  	return instance.host
   116  }
   117  
   118  func (instance *SInstance) GetIHostId() string {
   119  	if len(instance.LastHostUUID) > 0 {
   120  		return instance.LastHostUUID
   121  	}
   122  	return instance.HostUUID
   123  }
   124  
   125  func (instance *SInstance) GetId() string {
   126  	return instance.UUID
   127  }
   128  
   129  func (instance *SInstance) GetName() string {
   130  	return instance.Name
   131  }
   132  
   133  func (instance *SInstance) GetHostname() string {
   134  	return instance.Name
   135  }
   136  
   137  func (instance *SInstance) GetGlobalId() string {
   138  	return instance.GetId()
   139  }
   140  
   141  func (instance *SInstance) IsEmulated() bool {
   142  	return false
   143  }
   144  
   145  func (instance *SInstance) GetInstanceType() string {
   146  	if len(instance.InstanceOfferingUUID) > 0 {
   147  		offer, err := instance.host.zone.region.GetInstanceOffering(instance.InstanceOfferingUUID)
   148  		if err == nil {
   149  			return offer.Name
   150  		}
   151  	}
   152  	return instance.Type
   153  }
   154  
   155  func (instance *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
   156  	idisks := []cloudprovider.ICloudDisk{}
   157  	rootDisk, err := instance.host.zone.region.GetDiskWithStorage(instance.RootVolumeUUID)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	idisks = append(idisks, rootDisk)
   162  	for i := 0; i < len(instance.AllVolumes); i++ {
   163  		if instance.AllVolumes[i].UUID != instance.RootVolumeUUID {
   164  			dataDisk, err := instance.host.zone.region.GetDiskWithStorage(instance.AllVolumes[i].UUID)
   165  			if err != nil {
   166  				return nil, err
   167  			}
   168  			idisks = append(idisks, dataDisk)
   169  		}
   170  	}
   171  	return idisks, nil
   172  }
   173  
   174  func (instance *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
   175  	iNics := []cloudprovider.ICloudNic{}
   176  	for i := 0; i < len(instance.VMNics); i++ {
   177  		instance.VMNics[i].instance = instance
   178  		iNics = append(iNics, &instance.VMNics[i])
   179  	}
   180  	return iNics, nil
   181  }
   182  
   183  func (instance *SInstance) GetVcpuCount() int {
   184  	return instance.CPUNum
   185  }
   186  
   187  func (instance *SInstance) GetVmemSizeMB() int {
   188  	return instance.MemorySize / 1024 / 1024
   189  }
   190  
   191  func (instance *SInstance) GetBootOrder() string {
   192  	return instance.host.zone.region.GetBootOrder(instance.UUID)
   193  }
   194  
   195  func (region *SRegion) GetBootOrder(instanceId string) string {
   196  	resp, err := region.client.get("vm-instances", instanceId, "boot-orders")
   197  	if err != nil {
   198  		return "dcn"
   199  	}
   200  	orders := []string{}
   201  	err = resp.Unmarshal(&orders, "orders")
   202  	if err != nil {
   203  		return "dcn"
   204  	}
   205  	order := ""
   206  	for _, _order := range orders {
   207  		switch _order {
   208  		case "CdRom":
   209  			order += "c"
   210  		case "HardDisk":
   211  			order += "d"
   212  		default:
   213  			log.Errorf("Unknown BootOrder %s for instance %s", _order, instanceId)
   214  		}
   215  	}
   216  	return order
   217  }
   218  
   219  func (instance *SInstance) GetVga() string {
   220  	return "std"
   221  }
   222  
   223  func (instance *SInstance) GetVdi() string {
   224  	return "vnc"
   225  }
   226  
   227  func (instance *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo {
   228  	if instance.osInfo == nil {
   229  		osInfo := imagetools.NormalizeImageInfo(instance.Platform, "", "", "", "")
   230  		instance.osInfo = &osInfo
   231  	}
   232  	return instance.osInfo
   233  }
   234  
   235  func (instance *SInstance) GetOsType() cloudprovider.TOsType {
   236  	return cloudprovider.TOsType(instance.getNormalizedOsInfo().OsType)
   237  }
   238  
   239  func (instance *SInstance) GetFullOsName() string {
   240  	return instance.Platform
   241  }
   242  
   243  func (instance *SInstance) GetBios() cloudprovider.TBiosType {
   244  	return cloudprovider.ToBiosType(instance.getNormalizedOsInfo().OsBios)
   245  }
   246  
   247  func (instance *SInstance) GetOsDist() string {
   248  	return instance.getNormalizedOsInfo().OsDistro
   249  }
   250  
   251  func (instance *SInstance) GetOsVersion() string {
   252  	return instance.getNormalizedOsInfo().OsVersion
   253  }
   254  
   255  func (instance *SInstance) GetOsLang() string {
   256  	return instance.getNormalizedOsInfo().OsLang
   257  }
   258  
   259  func (instance *SInstance) GetOsArch() string {
   260  	return instance.getNormalizedOsInfo().OsArch
   261  }
   262  
   263  func (instance *SInstance) GetMachine() string {
   264  	return "pc"
   265  }
   266  
   267  func (instance *SInstance) GetStatus() string {
   268  	switch instance.State {
   269  	case "Stopped":
   270  		return api.VM_READY
   271  	case "Running":
   272  		return api.VM_RUNNING
   273  	case "Destroyed":
   274  		return api.VM_DEALLOCATED
   275  	default:
   276  		log.Errorf("Unknown instance %s status %s", instance.Name, instance.State)
   277  		return api.VM_UNKNOWN
   278  	}
   279  }
   280  
   281  func (instance *SInstance) Refresh() error {
   282  	new, err := instance.host.zone.region.GetInstance(instance.UUID)
   283  	if err != nil {
   284  		return err
   285  	}
   286  	return jsonutils.Update(instance, new)
   287  }
   288  
   289  func (instance *SInstance) GetHypervisor() string {
   290  	return api.HYPERVISOR_ZSTACK
   291  }
   292  
   293  func (instance *SInstance) StartVM(ctx context.Context) error {
   294  	err := instance.host.zone.region.StartVM(instance.UUID)
   295  	if err != nil {
   296  		return err
   297  	}
   298  	return cloudprovider.WaitStatus(instance, api.VM_RUNNING, 5*time.Second, 5*time.Minute)
   299  }
   300  
   301  func (region *SRegion) StartVM(instanceId string) error {
   302  	params := map[string]interface{}{
   303  		"startVmInstance": jsonutils.NewDict(),
   304  	}
   305  	_, err := region.client.put("vm-instances", instanceId, jsonutils.Marshal(params))
   306  	return err
   307  }
   308  
   309  func (instance *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
   310  	err := instance.host.zone.region.StopVM(instance.UUID, opts.IsForce)
   311  	if err != nil {
   312  		return err
   313  	}
   314  	return cloudprovider.WaitStatus(instance, api.VM_READY, 5*time.Second, 5*time.Minute)
   315  }
   316  
   317  func (region *SRegion) StopVM(instanceId string, isForce bool) error {
   318  	option := "grace"
   319  	if isForce {
   320  		option = "cold"
   321  	}
   322  	params := map[string]interface{}{
   323  		"stopVmInstance": map[string]string{
   324  			"type":   option,
   325  			"stopHA": "true",
   326  		},
   327  	}
   328  	_, err := region.client.put("vm-instances", instanceId, jsonutils.Marshal(params))
   329  	return err
   330  }
   331  
   332  func (instance *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
   333  	info, err := instance.host.zone.region.GetInstanceConsoleInfo(instance.UUID)
   334  	if err != nil {
   335  		return nil, err
   336  	}
   337  	authURL, _ := url.Parse(instance.host.zone.region.client.authURL)
   338  	url := fmt.Sprintf("%s://%s:5000/thirdparty/vnc_auto.html?host=%s&port=%d&token=%s&title=%s", info.Scheme, authURL.Hostname(), info.Hostname, info.Port, info.Token, instance.Name)
   339  	if ver, _ := instance.host.zone.region.client.GetVersion(); ver != nil {
   340  		if version.GE(ver.Version, "4.0.0") {
   341  			url = fmt.Sprintf("%s://%s:5000/novnc/index.html?host=%s&port=%d&token=%s&title=%s&language=zh-CN&lowVersion=false", info.Scheme, authURL.Hostname(), info.Hostname, info.Port, info.Token, instance.Name)
   342  		}
   343  	}
   344  	password, _ := instance.host.zone.region.GetInstanceConsolePassword(instance.UUID)
   345  	if len(password) > 0 {
   346  		url = url + fmt.Sprintf("&password=%s", password)
   347  	}
   348  	ret := &cloudprovider.ServerVncOutput{
   349  		Url:        url,
   350  		Protocol:   "zstack",
   351  		InstanceId: instance.UUID,
   352  		Hypervisor: api.HYPERVISOR_ZSTACK,
   353  	}
   354  	return ret, nil
   355  }
   356  
   357  func (instance *SInstance) UpdateVM(ctx context.Context, name string) error {
   358  	params := map[string]interface{}{
   359  		"updateVmInstance": map[string]string{
   360  			"name": name,
   361  		},
   362  	}
   363  	return instance.host.zone.region.UpdateVM(instance.UUID, jsonutils.Marshal(params))
   364  }
   365  
   366  func (region *SRegion) UpdateVM(instanceId string, params jsonutils.JSONObject) error {
   367  	_, err := region.client.put("vm-instances", instanceId, params)
   368  	return err
   369  }
   370  
   371  func (instance *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error {
   372  	if instance.Name != name || instance.Description != description {
   373  		params := map[string]interface{}{
   374  			"updateVmInstance": map[string]string{
   375  				"name":        name,
   376  				"description": description,
   377  			},
   378  		}
   379  		err := instance.host.zone.region.UpdateVM(instance.UUID, jsonutils.Marshal(params))
   380  		if err != nil {
   381  			return err
   382  		}
   383  	}
   384  	if len(password) > 0 {
   385  		params := map[string]interface{}{
   386  			"changeVmPassword": map[string]string{
   387  				"account":  username,
   388  				"password": password,
   389  			},
   390  		}
   391  		err := instance.host.zone.region.UpdateVM(instance.UUID, jsonutils.Marshal(params))
   392  		if err != nil {
   393  			return err
   394  		}
   395  	}
   396  	if len(publicKey) > 0 {
   397  		params := map[string]interface{}{
   398  			"setVmSshKey": map[string]string{
   399  				"SshKey": publicKey,
   400  			},
   401  		}
   402  		err := instance.host.zone.region.UpdateVM(instance.UUID, jsonutils.Marshal(params))
   403  		if err != nil {
   404  			return err
   405  		}
   406  	}
   407  	if deleteKeypair {
   408  		err := instance.host.zone.region.client.delete("vm-instances", fmt.Sprintf("%s/ssh-keys", instance.UUID), "")
   409  		if err != nil {
   410  			return err
   411  		}
   412  	}
   413  
   414  	return nil
   415  }
   416  
   417  func (instance *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
   418  	return instance.host.zone.region.RebuildRoot(instance.UUID, desc.ImageId, desc.SysSizeGB)
   419  }
   420  
   421  func (region *SRegion) RebuildRoot(instanceId, imageId string, sysSizeGB int) (string, error) {
   422  	params := map[string]interface{}{
   423  		"changeVmImage": map[string]string{
   424  			"imageUuid": imageId,
   425  		},
   426  	}
   427  	instance := &SInstance{}
   428  	resp, err := region.client.put("vm-instances", instanceId, jsonutils.Marshal(params))
   429  	if err != nil {
   430  		return "", err
   431  	}
   432  	err = resp.Unmarshal(instance, "inventory")
   433  	if err != nil {
   434  		return "", err
   435  	}
   436  	disk, err := region.GetDisk(instance.RootVolumeUUID)
   437  	if err != nil {
   438  		return "", err
   439  	}
   440  	if sysSizeGB > disk.GetDiskSizeMB()*1024 {
   441  		return instance.RootVolumeUUID, region.ResizeDisk(disk.UUID, int64(sysSizeGB)*1024)
   442  	}
   443  	return instance.RootVolumeUUID, nil
   444  }
   445  
   446  func (instance *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
   447  	offerings, err := instance.host.zone.region.GetInstanceOfferings("", "", config.Cpu, config.MemoryMB)
   448  	if err != nil {
   449  		return err
   450  	}
   451  	if len(config.InstanceType) > 0 {
   452  		for _, offering := range offerings {
   453  			if offering.Name == config.InstanceType {
   454  				return instance.host.zone.region.ChangeConfig(instance.UUID, offering.UUID)
   455  			}
   456  		}
   457  		offering, err := instance.host.zone.region.CreateInstanceOffering(config.InstanceType, config.Cpu, config.MemoryMB, "UserVm")
   458  		if err != nil {
   459  			return err
   460  		}
   461  		return instance.host.zone.region.ChangeConfig(instance.UUID, offering.UUID)
   462  	}
   463  	for _, offering := range offerings {
   464  		log.Debugf("try instance offering %s(%s) ...", offering.Name, offering.UUID)
   465  		err := instance.host.zone.region.ChangeConfig(instance.UUID, offering.UUID)
   466  		if err != nil {
   467  			log.Errorf("failed to change config for instance %s(%s) error: %v", instance.Name, instance.UUID, err)
   468  		} else {
   469  			return nil
   470  		}
   471  	}
   472  	return fmt.Errorf("Failed to change vm config, specification not supported")
   473  }
   474  
   475  func (region *SRegion) ChangeConfig(instanceId, offeringId string) error {
   476  	params := map[string]interface{}{
   477  		"changeInstanceOffering": map[string]string{
   478  			"instanceOfferingUuid": offeringId,
   479  		},
   480  	}
   481  	return region.UpdateVM(instanceId, jsonutils.Marshal(params))
   482  }
   483  
   484  func (instance *SInstance) AttachDisk(ctx context.Context, diskId string) error {
   485  	return instance.host.zone.region.AttachDisk(instance.UUID, diskId)
   486  }
   487  
   488  func (region *SRegion) AttachDisk(instanceId string, diskId string) error {
   489  	_, err := region.client.post(fmt.Sprintf("volumes/%s/vm-instances/%s", diskId, instanceId), jsonutils.NewDict())
   490  	return err
   491  }
   492  
   493  func (instance *SInstance) DetachDisk(ctx context.Context, diskId string) error {
   494  	return instance.host.zone.region.DetachDisk(instance.UUID, diskId)
   495  }
   496  
   497  func (region *SRegion) DetachDisk(instanceId, diskId string) error {
   498  	url := fmt.Sprintf("volumes/%s/vm-instances?vmUuid=%s", diskId, instanceId)
   499  	err := region.client.delete(url, "", "")
   500  	if err != nil && strings.Contains(err.Error(), "is not attached to any vm") {
   501  		return nil
   502  	}
   503  	return err
   504  }
   505  
   506  func (instance *SInstance) DeleteVM(ctx context.Context) error {
   507  	disks, err := instance.GetIDisks()
   508  	if err != nil {
   509  		return errors.Wrapf(err, "GetIDisks")
   510  	}
   511  	err = instance.host.zone.region.DeleteVM(instance.UUID)
   512  	if err != nil {
   513  		return errors.Wrapf(err, "DeleteVM")
   514  	}
   515  	for i := range disks {
   516  		if disks[i].GetDiskType() != api.DISK_TYPE_SYS && disks[i].GetIsAutoDelete() {
   517  			err = disks[i].Delete(ctx)
   518  			if err != nil {
   519  				log.Warningf("delete disk %s failed %s", disks[i].GetId(), err)
   520  			}
   521  		}
   522  	}
   523  	return nil
   524  }
   525  
   526  func (region *SRegion) DeleteVM(instanceId string) error {
   527  	err := region.client.delete("vm-instances", instanceId, "Enforcing")
   528  	if err != nil {
   529  		return err
   530  	}
   531  	params := map[string]interface{}{
   532  		"expungeVmInstance": jsonutils.NewDict(),
   533  	}
   534  	_, err = region.client.put("vm-instances", instanceId, jsonutils.Marshal(params))
   535  	if err != nil {
   536  		return errors.Wrapf(err, "expungeVmInstance")
   537  	}
   538  	return nil
   539  }
   540  
   541  func (instance *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
   542  	eips, err := instance.host.zone.region.GetEips("", instance.UUID)
   543  	if err != nil {
   544  		return nil, err
   545  	}
   546  	if len(eips) == 0 {
   547  		return nil, cloudprovider.ErrNotFound
   548  	}
   549  	if len(eips) == 1 {
   550  		return &eips[0], nil
   551  	}
   552  	return nil, cloudprovider.ErrDuplicateId
   553  }
   554  
   555  func (instance *SInstance) AssignSecurityGroup(secgroupId string) error {
   556  	return instance.host.zone.region.AssignSecurityGroup(instance.UUID, secgroupId)
   557  }
   558  
   559  func (instance *SInstance) SetSecurityGroups(secgroupIds []string) error {
   560  	currentIds, err := instance.GetSecurityGroupIds()
   561  	if err != nil {
   562  		return err
   563  	}
   564  	for _, id := range currentIds {
   565  		if !utils.IsInStringArray(id, secgroupIds) {
   566  			err := instance.host.zone.region.RevokeSecurityGroup(instance.UUID, id)
   567  			if err != nil {
   568  				return err
   569  			}
   570  		}
   571  	}
   572  	for _, id := range secgroupIds {
   573  		if !utils.IsInStringArray(id, currentIds) {
   574  			err := instance.host.zone.region.AssignSecurityGroup(instance.UUID, id)
   575  			if err != nil {
   576  				return err
   577  			}
   578  		}
   579  	}
   580  	return nil
   581  }
   582  
   583  func (region *SRegion) AssignSecurityGroup(instanceId, secgroupId string) error {
   584  	instance, err := region.GetInstance(instanceId)
   585  	if err != nil {
   586  		return err
   587  	}
   588  	secgroup, err := region.GetSecurityGroup(secgroupId)
   589  	if err != nil {
   590  		return err
   591  	}
   592  	if len(instance.VMNics) > 0 {
   593  		if !utils.IsInStringArray(instance.VMNics[0].L3NetworkUUID, secgroup.AttachedL3NetworkUUIDs) {
   594  			resource := fmt.Sprintf("security-groups/%s/l3-networks/%s", secgroupId, instance.VMNics[0].L3NetworkUUID)
   595  			_, err := region.client.post(resource, jsonutils.NewDict())
   596  			if err != nil {
   597  				return err
   598  			}
   599  		}
   600  		params := map[string]interface{}{
   601  			"params": map[string]interface{}{
   602  				"vmNicUuids": []string{instance.VMNics[0].UUID},
   603  			},
   604  		}
   605  		resource := fmt.Sprintf("security-groups/%s/vm-instances/nics", secgroupId)
   606  		_, err = region.client.post(resource, jsonutils.Marshal(params))
   607  		return err
   608  	}
   609  	return nil
   610  }
   611  
   612  func (region *SRegion) RevokeSecurityGroup(instanceId, secgroupId string) error {
   613  	instance, err := region.GetInstance(instanceId)
   614  	if err != nil {
   615  		return err
   616  	}
   617  	for _, nic := range instance.VMNics {
   618  		resource := fmt.Sprintf("security-groups/%s/vm-instances/nics?vmNicUuids=%s", secgroupId, nic.UUID)
   619  		err := region.client.delete(resource, "", "")
   620  		if err != nil {
   621  			return err
   622  		}
   623  	}
   624  	return nil
   625  }
   626  
   627  func (instance *SInstance) GetBillingType() string {
   628  	return ""
   629  }
   630  
   631  func (instance *SInstance) GetCreatedAt() time.Time {
   632  	return instance.CreateDate
   633  }
   634  
   635  func (instance *SInstance) GetExpiredAt() time.Time {
   636  	return time.Time{}
   637  }
   638  
   639  func (instance *SInstance) UpdateUserData(userData string) error {
   640  	return cloudprovider.ErrNotSupported
   641  }
   642  
   643  func (instance *SInstance) Renew(bc billing.SBillingCycle) error {
   644  	return cloudprovider.ErrNotSupported
   645  }
   646  
   647  func (instance *SInstance) GetProjectId() string {
   648  	return ""
   649  }
   650  
   651  func (instance *SInstance) GetError() error {
   652  	return nil
   653  }
   654  
   655  type SConsoleInfo struct {
   656  	Scheme   string `json:"scheme"`
   657  	Hostname string `json:"hostname"`
   658  	Port     int    `json:"port"`
   659  	Token    string `json:"token"`
   660  }
   661  
   662  func (region *SRegion) GetInstanceConsoleInfo(instnaceId string) (*SConsoleInfo, error) {
   663  	params := map[string]interface{}{
   664  		"params": map[string]string{
   665  			"vmInstanceUuid": instnaceId,
   666  		},
   667  	}
   668  	resp, err := region.client.post("consoles", jsonutils.Marshal(params))
   669  	if err != nil {
   670  		return nil, err
   671  	}
   672  	info := &SConsoleInfo{}
   673  	err = resp.Unmarshal(info, "inventory")
   674  	if err != nil {
   675  		return nil, err
   676  	}
   677  	return info, nil
   678  }
   679  
   680  func (region *SRegion) GetInstanceConsolePassword(instnaceId string) (string, error) {
   681  	resp, err := region.client.get("vm-instances", instnaceId, "console-passwords")
   682  	if err != nil {
   683  		return "", err
   684  	}
   685  	if resp.Contains("consolePassword") {
   686  		return resp.GetString("consolePassword")
   687  	}
   688  	return "", nil
   689  }