yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/openstack/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 openstack
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net/url"
    21  	"time"
    22  
    23  	"gopkg.in/fatih/set.v0"
    24  
    25  	"yunion.io/x/jsonutils"
    26  	"yunion.io/x/log"
    27  	"yunion.io/x/pkg/errors"
    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  const (
    37  	INSTANCE_STATUS_ACTIVE            = "ACTIVE"            //The server is active.
    38  	INSTANCE_STATUS_BUILD             = "BUILD"             //The server has not finished the original build process.
    39  	INSTANCE_STATUS_DELETED           = "DELETED"           //The server is permanently deleted.
    40  	INSTANCE_STATUS_ERROR             = "ERROR"             //The server is in error.
    41  	INSTANCE_STATUS_HARD_REBOOT       = "HARD_REBOOT"       //The server is hard rebooting. This is equivalent to pulling the power plug on a physical server, plugging it back in, and rebooting it.
    42  	INSTANCE_STATUS_MIGRATING         = "MIGRATING"         //The server is being migrated to a new host.
    43  	INSTANCE_STATUS_PASSWORD          = "PASSWORD"          //The password is being reset on the server.
    44  	INSTANCE_STATUS_PAUSED            = "PAUSED"            //In a paused state, the state of the server is stored in RAM.A paused server continues to run in frozen state.
    45  	INSTANCE_STATUS_REBOOT            = "REBOOT"            //The server is in a soft reboot state. A reboot command was passed to the operating system.
    46  	INSTANCE_STATUS_REBUILD           = "REBUILD"           //The server is currently being rebuilt from an image.
    47  	INSTANCE_STATUS_RESCUE            = "RESCUE"            //The server is in rescue mode. A rescue image is running with the original server image attached.
    48  	INSTANCE_STATUS_RESIZE            = "RESIZE"            //Server is performing the differential copy of data that changed during its initial copy. Server is down for this stage.
    49  	INSTANCE_STATUS_REVERT_RESIZE     = "REVERT_RESIZE"     //The resize or migration of a server failed for some reason. The destination server is being cleaned up and the original source server is restarting.
    50  	INSTANCE_STATUS_SHELVED           = "SHELVED"           // The server is in shelved state. Depending on the shelve offload time, the server will be automatically shelved offloaded.
    51  	INSTANCE_STATUS_SHELVED_OFFLOADED = "SHELVED_OFFLOADED" // The shelved server is offloaded (removed from the compute host) and it needs unshelved action to be used again.
    52  	INSTANCE_STATUS_SHUTOFF           = "SHUTOFF"           //The server is powered off and the disk image still persists.
    53  	INSTANCE_STATUS_SOFT_DELETED      = "SOFT_DELETED"      //The server is marked as deleted but the disk images are still available to restore.
    54  	INSTANCE_STATUS_SUSPENDED         = "SUSPENDED"         //The server is suspended, either by request or necessity. This status appears for only the XenServer/XCP, KVM, and ESXi hypervisors. Administrative users can suspend an instance if it is infrequently used or to perform system maintenance. When you suspend an instance, its VM state is stored on disk, all memory is written to disk, and the virtual machine is stopped. Suspending an instance is similar to placing a device in hibernation; memory and vCPUs become available to create other instances.
    55  	INSTANCE_STATUS_UNKNOWN           = "UNKNOWN"           //The state of the server is unknown. Contact your cloud provider.
    56  	INSTANCE_STATUS_VERIFY_RESIZE     = "VERIFY_RESIZE"     //System is awaiting confirmation that the server is operational after a move or resize.
    57  )
    58  
    59  type SPrivate struct {
    60  	MacAddr string `json:"OS-EXT-IPS-MAC:mac_addr,omitempty"`
    61  	Addr    string `json:"OS-EXT-IPS:type,omitempty"`
    62  	Version int
    63  }
    64  
    65  type SecurityGroup struct {
    66  	Id          string
    67  	Name        string
    68  	Description string
    69  }
    70  
    71  type ExtraSpecs struct {
    72  	CpuPolicy   string `json:"hw:cpu_policy,omitempty"`
    73  	MemPageSize string `json:"hw:mem_page_size,omitempty"`
    74  }
    75  
    76  type Resource struct {
    77  	Id    string
    78  	Links []Link
    79  }
    80  
    81  type VolumesAttached struct {
    82  	Id                  string
    83  	DeleteOnTermination bool
    84  }
    85  
    86  type SFault struct {
    87  	Message string
    88  	Code    int
    89  	Details string
    90  }
    91  
    92  type SInstance struct {
    93  	multicloud.SInstanceBase
    94  	OpenStackTags
    95  	host *SHypervisor
    96  
    97  	imageObj *SImage
    98  
    99  	DiskConfig         string    `json:"OS-DCF:diskConfig,omitempty"`
   100  	AvailabilityZone   string    `json:"OS-EXT-AZ:availability_zone,omitempty"`
   101  	Host               string    `json:"OS-EXT-SRV-ATTR:host,omitempty"`
   102  	Hostname           string    `json:"OS-EXT-SRV-ATTR:hostname,omitempty"`
   103  	HypervisorHostname string    `json:"OS-EXT-SRV-ATTR:hypervisor_hostname,omitempty"`
   104  	InstanceName       string    `json:"OS-EXT-SRV-ATTR:instance_name,omitempty"`
   105  	KernelId           string    `json:"OS-EXT-SRV-ATTR:kernel_id,omitempty"`
   106  	LaunchIndex        int       `json:"OS-EXT-SRV-ATTR:launch_index,omitempty"`
   107  	RamdiskId          string    `json:"OS-EXT-SRV-ATTR:ramdisk_id,omitempty"`
   108  	ReservationId      string    `json:"OS-EXT-SRV-ATTR:reservation_id,omitempty"`
   109  	RootDeviceName     string    `json:"OS-EXT-SRV-ATTR:root_device_name,omitempty"`
   110  	UserData           string    `json:"OS-EXT-SRV-ATTR:user_data,omitempty"`
   111  	PowerState         int       `json:"OS-EXT-STS:power_state,omitempty"`
   112  	TaskState          string    `json:"OS-EXT-STS:task_state,omitempty"`
   113  	VmState            string    `json:"OS-EXT-STS:vm_state,omitempty"`
   114  	LaunchedAt         time.Time `json:"OS-SRV-USG:launched_at,omitempty"`
   115  	TerminatedAt       string    `json:"OS-SRV-USG:terminated_at,omitempty"`
   116  
   117  	AccessIPv4               string
   118  	AccessIPv6               string
   119  	Addresses                map[string][]SInstanceNic
   120  	ConfigDrive              string
   121  	Created                  time.Time
   122  	Description              string
   123  	Flavor                   SFlavor
   124  	HostId                   string
   125  	HostStatus               string
   126  	Id                       string
   127  	Image                    jsonutils.JSONObject `json:"image"` //有可能是字符串
   128  	KeyName                  string
   129  	Links                    []Link
   130  	Locked                   bool
   131  	Name                     string
   132  	VolumesAttached          []VolumesAttached `json:"os-extended-volumes:volumes_attached,omitempty"`
   133  	Progress                 int
   134  	SecurityGroups           []SecurityGroup
   135  	Status                   string
   136  	Tags                     []string
   137  	TenantId                 string
   138  	TrustedImageCertificates []string
   139  	Updated                  time.Time
   140  	UserId                   string
   141  	Fault                    SFault
   142  }
   143  
   144  func (region *SRegion) GetSecurityGroupsByInstance(instanceId string) ([]SecurityGroup, error) {
   145  	resource := fmt.Sprintf("/servers/%s/os-security-groups", instanceId)
   146  	resp, err := region.ecsGet(resource)
   147  	if err != nil {
   148  		return nil, errors.Wrap(err, "ecsGet")
   149  	}
   150  	secgroups := []SecurityGroup{}
   151  	err = resp.Unmarshal(&secgroups, "security_groups")
   152  	if err != nil {
   153  		return nil, errors.Wrap(err, "resp.Unmarshal")
   154  	}
   155  	return secgroups, nil
   156  }
   157  
   158  func (region *SRegion) GetInstances(host string) ([]SInstance, error) {
   159  	instances := []SInstance{}
   160  	resource := "/servers/detail"
   161  	query := url.Values{}
   162  	query.Set("all_tenants", "True")
   163  	for {
   164  		resp, err := region.ecsList(resource, query)
   165  		if err != nil {
   166  			return nil, errors.Wrap(err, "ecsList")
   167  		}
   168  		part := struct {
   169  			Servers      []SInstance
   170  			ServersLinks SNextLinks
   171  		}{}
   172  		err = resp.Unmarshal(&part)
   173  		if err != nil {
   174  			return nil, errors.Wrap(err, "resp.Unmarshal")
   175  		}
   176  		for i := range part.Servers {
   177  			if len(host) == 0 || part.Servers[i].Host == host || part.Servers[i].HypervisorHostname == host {
   178  				instances = append(instances, part.Servers[i])
   179  			}
   180  		}
   181  		marker := part.ServersLinks.GetNextMark()
   182  		if len(marker) == 0 {
   183  			break
   184  		}
   185  		query.Set("marker", marker)
   186  	}
   187  	return instances, nil
   188  }
   189  
   190  func (region *SRegion) GetInstance(instanceId string) (*SInstance, error) {
   191  	resource := "/servers/" + instanceId
   192  	resp, err := region.ecsGet(resource)
   193  	if err != nil {
   194  		return nil, errors.Wrap(err, "ecsGet")
   195  	}
   196  	instance := &SInstance{}
   197  	err = resp.Unmarshal(instance, "server")
   198  	if err != nil {
   199  		return nil, errors.Wrap(err, "resp.Unmarsha")
   200  	}
   201  	return instance, nil
   202  }
   203  
   204  func (instance *SInstance) GetSecurityGroupIds() ([]string, error) {
   205  	secgroupIds := []string{}
   206  	secgroups, err := instance.host.zone.region.GetSecurityGroupsByInstance(instance.Id)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	for _, secgroup := range secgroups {
   211  		secgroupIds = append(secgroupIds, secgroup.Id)
   212  	}
   213  	return secgroupIds, nil
   214  }
   215  
   216  func (instance *SInstance) GetIHost() cloudprovider.ICloudHost {
   217  	return instance.host
   218  }
   219  
   220  func (instance *SInstance) GetId() string {
   221  	return instance.Id
   222  }
   223  
   224  func (instance *SInstance) GetName() string {
   225  	return instance.Name
   226  }
   227  
   228  func (instance *SInstance) GetHostname() string {
   229  	return instance.Hostname
   230  }
   231  
   232  func (instance *SInstance) GetGlobalId() string {
   233  	return instance.Id
   234  }
   235  
   236  func (instance *SInstance) IsEmulated() bool {
   237  	return false
   238  }
   239  
   240  func (instance *SInstance) fetchFlavor() error {
   241  	if len(instance.Flavor.Id) > 0 && instance.Flavor.Vcpus == 0 {
   242  		flavor, err := instance.host.zone.region.GetFlavor(instance.Flavor.Id)
   243  		if err != nil {
   244  			return err
   245  		}
   246  		instance.Flavor = *flavor
   247  	}
   248  	return nil
   249  }
   250  
   251  func (instance *SInstance) GetInstanceType() string {
   252  	err := instance.fetchFlavor()
   253  	if err != nil {
   254  		return ""
   255  	}
   256  	return instance.Flavor.GetName()
   257  }
   258  
   259  func (instance *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
   260  	disks := []SDisk{}
   261  	hasSysDisk := false
   262  	for i := 0; i < len(instance.VolumesAttached); i++ {
   263  		disk, err := instance.host.zone.region.GetDisk(instance.VolumesAttached[i].Id)
   264  		if err != nil {
   265  			return nil, errors.Wrapf(err, "GetDisk(%s)", instance.VolumesAttached[i].Id)
   266  		}
   267  		disks = append(disks, *disk)
   268  		if disk.GetDiskType() == api.DISK_TYPE_SYS {
   269  			hasSysDisk = true
   270  		}
   271  	}
   272  	idisks := []cloudprovider.ICloudDisk{}
   273  	for i := 0; i < len(disks); i++ {
   274  		store, err := instance.host.zone.getStorageByCategory(disks[i].VolumeType, disks[i].Host)
   275  		if err != nil {
   276  			return nil, errors.Wrapf(err, "getStorageByCategory(%s.%s)", disks[i].Id, disks[i].VolumeType)
   277  		}
   278  		disks[i].storage = store
   279  		idisks = append(idisks, &disks[i])
   280  	}
   281  
   282  	if !hasSysDisk {
   283  		store := &SNovaStorage{zone: instance.host.zone, host: instance.host}
   284  		disk := &SNovaDisk{storage: store, instanceId: instance.Id, region: instance.host.zone.region}
   285  		idisks = append([]cloudprovider.ICloudDisk{disk}, idisks...)
   286  	}
   287  
   288  	return idisks, nil
   289  }
   290  
   291  func (instance *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
   292  	nics, err := instance.host.zone.region.GetInstancePorts(instance.Id)
   293  	if err != nil {
   294  		return nil, errors.Wrap(err, "GetInstancePorts")
   295  	}
   296  	inics := []cloudprovider.ICloudNic{}
   297  	for i := range nics {
   298  		nics[i].region = instance.host.zone.region
   299  		inics = append(inics, &nics[i])
   300  	}
   301  	return inics, nil
   302  }
   303  
   304  func (instance *SInstance) GetVcpuCount() int {
   305  	err := instance.fetchFlavor()
   306  	if err != nil {
   307  		return 0
   308  	}
   309  	return instance.Flavor.Vcpus
   310  }
   311  
   312  func (instance *SInstance) GetVmemSizeMB() int {
   313  	err := instance.fetchFlavor()
   314  	if err != nil {
   315  		return 0
   316  	}
   317  	return instance.Flavor.RAM
   318  }
   319  
   320  func (instance *SInstance) GetBootOrder() string {
   321  	return "dcn"
   322  }
   323  
   324  func (instance *SInstance) GetVga() string {
   325  	return "std"
   326  }
   327  
   328  func (instance *SInstance) GetVdi() string {
   329  	return "vnc"
   330  }
   331  
   332  func (instance *SInstance) getImage() *SImage {
   333  	if instance.imageObj == nil && instance.Image != nil {
   334  		imageId, _ := instance.Image.GetString("id")
   335  		if len(imageId) == 0 {
   336  			imageId, _ = instance.Image.GetString()
   337  		}
   338  		if len(imageId) > 0 {
   339  			image, _ := instance.host.zone.region.GetImage(imageId)
   340  			if image != nil {
   341  				instance.imageObj = image
   342  			}
   343  		}
   344  	}
   345  	return instance.imageObj
   346  }
   347  
   348  func (instance *SInstance) GetOsType() cloudprovider.TOsType {
   349  	img := instance.getImage()
   350  	if img != nil {
   351  		return img.GetOsType()
   352  	}
   353  	return cloudprovider.OsTypeLinux
   354  }
   355  
   356  func (instance *SInstance) GetFullOsName() string {
   357  	img := instance.getImage()
   358  	if img != nil {
   359  		return img.GetFullOsName()
   360  	}
   361  	return ""
   362  }
   363  
   364  func (instance *SInstance) GetBios() cloudprovider.TBiosType {
   365  	img := instance.getImage()
   366  	if img != nil {
   367  		return img.GetBios()
   368  	}
   369  	return "BIOS"
   370  }
   371  
   372  func (instance *SInstance) GetOsDist() string {
   373  	img := instance.getImage()
   374  	if img != nil {
   375  		return img.GetOsDist()
   376  	}
   377  	return ""
   378  }
   379  
   380  func (instance *SInstance) GetOsVersion() string {
   381  	img := instance.getImage()
   382  	if img != nil {
   383  		return img.GetOsVersion()
   384  	}
   385  	return ""
   386  }
   387  
   388  func (instance *SInstance) GetOsLang() string {
   389  	img := instance.getImage()
   390  	if img != nil {
   391  		return img.GetOsLang()
   392  	}
   393  	return ""
   394  }
   395  
   396  func (instance *SInstance) GetOsArch() string {
   397  	img := instance.getImage()
   398  	if img != nil {
   399  		return img.GetOsArch()
   400  	}
   401  	return ""
   402  }
   403  
   404  func (instance *SInstance) GetMachine() string {
   405  	return "pc"
   406  }
   407  
   408  func (instance *SInstance) GetStatus() string {
   409  	switch instance.Status {
   410  	case INSTANCE_STATUS_ACTIVE, INSTANCE_STATUS_RESCUE:
   411  		return api.VM_RUNNING
   412  	case INSTANCE_STATUS_BUILD, INSTANCE_STATUS_PASSWORD:
   413  		return api.VM_DEPLOYING
   414  	case INSTANCE_STATUS_DELETED:
   415  		return api.VM_DELETING
   416  	case INSTANCE_STATUS_HARD_REBOOT, INSTANCE_STATUS_REBOOT:
   417  		return api.VM_STARTING
   418  	case INSTANCE_STATUS_MIGRATING:
   419  		return api.VM_MIGRATING
   420  	case INSTANCE_STATUS_PAUSED, INSTANCE_STATUS_SUSPENDED:
   421  		return api.VM_SUSPEND
   422  	case INSTANCE_STATUS_RESIZE:
   423  		return api.VM_CHANGE_FLAVOR
   424  	case INSTANCE_STATUS_VERIFY_RESIZE:
   425  		// API请求更改配置后,状态先回变更到 INSTANCE_STATUS_RESIZE 等待一会变成此状态
   426  		// 到达此状态后需要再次发送确认请求,变更才会生效
   427  		// 此状态不能和INSTANCE_STATUS_RESIZE返回一样,避免在INSTANCE_STATUS_RESIZE状态下发送确认请求,导致更改配置失败
   428  		return api.VM_SYNC_CONFIG
   429  	case INSTANCE_STATUS_SHELVED, INSTANCE_STATUS_SHELVED_OFFLOADED, INSTANCE_STATUS_SHUTOFF, INSTANCE_STATUS_SOFT_DELETED:
   430  		return api.VM_READY
   431  	default:
   432  		return api.VM_UNKNOWN
   433  	}
   434  }
   435  
   436  func (instance *SInstance) Refresh() error {
   437  	_instance, err := instance.host.zone.region.GetInstance(instance.Id)
   438  	if err != nil {
   439  		return err
   440  	}
   441  	return jsonutils.Update(instance, _instance)
   442  }
   443  
   444  func (instance *SInstance) UpdateVM(ctx context.Context, name string) error {
   445  	if instance.Name != name {
   446  		params := map[string]map[string]string{
   447  			"server": {
   448  				"name": name,
   449  			},
   450  		}
   451  		resource := "/servers/" + instance.Id
   452  		_, err := instance.host.zone.region.ecsUpdate(resource, params)
   453  		return err
   454  	}
   455  	return nil
   456  }
   457  
   458  func (instance *SInstance) GetHypervisor() string {
   459  	return api.HYPERVISOR_OPENSTACK
   460  }
   461  
   462  func (instance *SInstance) StartVM(ctx context.Context) error {
   463  	err := instance.host.zone.region.StartVM(instance.Id)
   464  	if err != nil {
   465  		return errors.Wrapf(err, "StartVM(%s)", instance.Id)
   466  	}
   467  	return cloudprovider.WaitStatus(instance, api.VM_RUNNING, 10*time.Second, 8*time.Minute)
   468  }
   469  
   470  func (instance *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
   471  	err := instance.host.zone.region.StopVM(instance.Id, opts.IsForce)
   472  	if err != nil {
   473  		return errors.Wrapf(err, "StopVM(%s)", instance.Id)
   474  	}
   475  	return cloudprovider.WaitStatus(instance, api.VM_READY, 10*time.Second, 8*time.Minute)
   476  }
   477  
   478  func (region *SRegion) GetInstanceVNCUrl(instanceId string, origin bool) (*cloudprovider.ServerVncOutput, error) {
   479  	params := map[string]map[string]string{
   480  		"remote_console": {
   481  			"protocol": "vnc",
   482  			"type":     "novnc",
   483  		},
   484  	}
   485  	resource := fmt.Sprintf("/servers/%s/remote-consoles", instanceId)
   486  	resp, err := region.ecsPost(resource, params)
   487  	if err != nil {
   488  		return nil, errors.Wrap(err, "ecsPost")
   489  	}
   490  	ret := &cloudprovider.ServerVncOutput{
   491  		Protocol:   "openstack",
   492  		InstanceId: instanceId,
   493  		Hypervisor: api.HYPERVISOR_OPENSTACK,
   494  	}
   495  
   496  	ret.Url, err = resp.GetString("remote_console", "url")
   497  	if err != nil {
   498  		return nil, errors.Wrapf(err, "remote_console")
   499  	}
   500  
   501  	if origin {
   502  		return ret, nil
   503  	}
   504  
   505  	token := string([]byte(ret.Url)[len(ret.Url)-36:])
   506  	vncUrl, _ := url.Parse(ret.Url)
   507  	ret.Url = fmt.Sprintf("ws://%s?token=%s", vncUrl.Host, token)
   508  	ret.Protocol = "vnc"
   509  	return ret, nil
   510  }
   511  
   512  func (region *SRegion) GetInstanceVNC(instanceId string, origin bool) (*cloudprovider.ServerVncOutput, error) {
   513  	params := map[string]map[string]string{
   514  		"os-getVNCConsole": {
   515  			"type": "novnc",
   516  		},
   517  	}
   518  	resource := fmt.Sprintf("/servers/%s/action", instanceId)
   519  	resp, err := region.ecsPost(resource, params)
   520  	if err != nil {
   521  		return nil, errors.Wrap(err, "ecsPost")
   522  	}
   523  	ret := &cloudprovider.ServerVncOutput{
   524  		Protocol:   "openstack",
   525  		InstanceId: instanceId,
   526  		Hypervisor: api.HYPERVISOR_OPENSTACK,
   527  	}
   528  
   529  	ret.Url, err = resp.GetString("console", "url")
   530  	if err != nil {
   531  		return nil, errors.Wrapf(err, "remote_console")
   532  	}
   533  
   534  	if origin {
   535  		return ret, nil
   536  	}
   537  
   538  	token := string([]byte(ret.Url)[len(ret.Url)-36:])
   539  	vncUrl, _ := url.Parse(ret.Url)
   540  	ret.Url = fmt.Sprintf("ws://%s?token=%s", vncUrl.Host, token)
   541  	ret.Protocol = "vnc"
   542  	return ret, nil
   543  }
   544  
   545  func (instance *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
   546  	origin := false
   547  	if input != nil {
   548  		origin = input.Origin
   549  	}
   550  	ret, err := instance.host.zone.region.GetInstanceVNCUrl(instance.Id, origin)
   551  	if err == nil {
   552  		return ret, nil
   553  	}
   554  	return instance.host.zone.region.GetInstanceVNC(instance.Id, origin)
   555  }
   556  
   557  func (instance *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error {
   558  	return instance.host.zone.region.DeployVM(instance.Id, name, password, publicKey, deleteKeypair, description)
   559  }
   560  
   561  func (instance *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
   562  	return instance.Id, instance.host.zone.region.ReplaceSystemDisk(instance.Id, desc.ImageId, desc.Password, desc.PublicKey, desc.SysSizeGB)
   563  }
   564  
   565  func (instance *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
   566  	if (len(config.InstanceType) > 0 && instance.GetInstanceType() != config.InstanceType) || instance.GetVcpuCount() != config.Cpu || instance.GetVmemSizeMB() != config.MemoryMB {
   567  		flavor, err := instance.host.zone.region.syncFlavor(config.InstanceType, config.Cpu, config.MemoryMB, 40)
   568  		if err != nil {
   569  			return errors.Wrapf(err, "syncFlavor(%s)", config.InstanceType)
   570  		}
   571  		// When resizing, instances must change flavor!
   572  		if flavor.Name == instance.Flavor.OriginalName {
   573  			return nil
   574  		}
   575  		return instance.host.zone.region.ChangeConfig(instance, flavor.Id)
   576  	}
   577  	return nil
   578  }
   579  
   580  func (region *SRegion) ChangeConfig(instance *SInstance, flavorId string) error {
   581  	params := map[string]map[string]string{
   582  		"resize": {
   583  			"flavorRef": flavorId,
   584  		},
   585  	}
   586  	resource := fmt.Sprintf("/servers/%s/action", instance.Id)
   587  	_, err := region.ecsPost(resource, params)
   588  	if err != nil {
   589  		return errors.Wrap(err, "ecsPost")
   590  	}
   591  	err = cloudprovider.WaitStatus(instance, api.VM_SYNC_CONFIG, time.Second*3, time.Minute*4)
   592  	if err != nil {
   593  		return errors.Wrap(err, "WaitStatsAfterChangeConfig")
   594  	}
   595  	return region.instanceOperation(instance.Id, "confirmResize")
   596  }
   597  
   598  func (instance *SInstance) AttachDisk(ctx context.Context, diskId string) error {
   599  	return instance.host.zone.region.AttachDisk(instance.Id, diskId)
   600  }
   601  
   602  func (instance *SInstance) DetachDisk(ctx context.Context, diskId string) error {
   603  	return instance.host.zone.region.DetachDisk(instance.Id, diskId)
   604  }
   605  
   606  func (region *SRegion) instanceOperation(instanceId, operate string) error {
   607  	params := map[string]string{operate: ""}
   608  	resource := fmt.Sprintf("/servers/%s/action", instanceId)
   609  	_, err := region.ecsPost(resource, params)
   610  	return err
   611  }
   612  
   613  func (region *SRegion) doStopVM(instanceId string, isForce bool) error {
   614  	return region.instanceOperation(instanceId, "os-stop")
   615  }
   616  
   617  func (region *SRegion) doDeleteVM(instanceId string) error {
   618  	return region.instanceOperation(instanceId, "forceDelete")
   619  }
   620  
   621  func (region *SRegion) StartVM(instanceId string) error {
   622  	return region.instanceOperation(instanceId, "os-start")
   623  }
   624  
   625  func (region *SRegion) StopVM(instanceId string, isForce bool) error {
   626  	return region.doStopVM(instanceId, isForce)
   627  }
   628  
   629  func (region *SRegion) DeleteVM(instanceId string) error {
   630  	instance, err := region.GetInstance(instanceId)
   631  	if err != nil {
   632  		if errors.Cause(err) == cloudprovider.ErrNotFound {
   633  			return nil
   634  		}
   635  		return errors.Wrapf(err, "GetInstance(%s)", instanceId)
   636  	}
   637  	status := instance.GetStatus()
   638  	log.Debugf("Instance status on delete is %s", status)
   639  	if status != api.VM_READY {
   640  		log.Warningf("DeleteVM: vm status is %s expect %s", status, api.VM_READY)
   641  	}
   642  	return region.doDeleteVM(instanceId)
   643  }
   644  
   645  func (region *SRegion) DeployVM(instanceId string, name string, password string, keypairName string, deleteKeypair bool, description string) error {
   646  	if len(password) > 0 {
   647  		params := map[string]map[string]string{
   648  			"changePassword": {
   649  				"adminPass": password,
   650  			},
   651  		}
   652  		resource := fmt.Sprintf("/servers/%s/action", instanceId)
   653  		_, err := region.ecsPost(resource, params)
   654  		return err
   655  	}
   656  	return nil
   657  }
   658  
   659  func (instance *SInstance) DeleteVM(ctx context.Context) error {
   660  	err := instance.host.zone.region.DeleteVM(instance.Id)
   661  	if err != nil {
   662  		return errors.Wrapf(err, "instance.host.zone.region.DeleteVM(%s)", instance.Id)
   663  	}
   664  	return cloudprovider.WaitDeleted(instance, time.Second*5, time.Minute*10)
   665  }
   666  
   667  func (region *SRegion) ReplaceSystemDisk(instanceId string, imageId string, passwd string, publicKey string, sysDiskSizeGB int) error {
   668  	params := map[string]map[string]string{
   669  		"rebuild": {
   670  			"imageRef": imageId,
   671  		},
   672  	}
   673  
   674  	if len(publicKey) > 0 {
   675  		keypairName, err := region.syncKeypair(instanceId, publicKey)
   676  		if err != nil {
   677  			return err
   678  		}
   679  		params["rebuild"]["key_name"] = keypairName
   680  	}
   681  
   682  	if len(passwd) > 0 {
   683  		params["rebuild"]["adminPass"] = passwd
   684  	}
   685  	resource := fmt.Sprintf("/servers/%s/action", instanceId)
   686  	_, err := region.ecsPost(resource, params)
   687  	return err
   688  }
   689  
   690  func (region *SRegion) DetachDisk(instanceId string, diskId string) error {
   691  	resource := fmt.Sprintf("/servers/%s/os-volume_attachments/%s", instanceId, diskId)
   692  	_, err := region.ecsDelete(resource)
   693  	if err != nil {
   694  		return errors.Wrap(err, "ecsDelete")
   695  	}
   696  	status := ""
   697  	startTime := time.Now()
   698  	for time.Now().Sub(startTime) < time.Minute*10 {
   699  		disk, err := region.GetDisk(diskId)
   700  		if err != nil {
   701  			return errors.Wrapf(err, "GetDisk(%s)", diskId)
   702  		}
   703  		status = disk.Status
   704  		log.Debugf("status %s expect %s", status, DISK_STATUS_AVAILABLE)
   705  		if status == DISK_STATUS_AVAILABLE {
   706  			return nil
   707  		}
   708  		time.Sleep(time.Second * 15)
   709  	}
   710  	return fmt.Errorf("timeout for waitting detach disk, current status: %s", status)
   711  }
   712  
   713  func (region *SRegion) AttachDisk(instanceId string, diskId string) error {
   714  	params := map[string]map[string]string{
   715  		"volumeAttachment": {
   716  			"volumeId": diskId,
   717  		},
   718  	}
   719  	resource := fmt.Sprintf("/servers/%s/os-volume_attachments", instanceId)
   720  	_, err := region.ecsPost(resource, params)
   721  	if err != nil {
   722  		return errors.Wrap(err, "ecsPost")
   723  	}
   724  	status := ""
   725  	startTime := time.Now()
   726  	for time.Now().Sub(startTime) < time.Minute*10 {
   727  		disk, err := region.GetDisk(diskId)
   728  		if err != nil {
   729  			return errors.Wrapf(err, "GetDisk(%s)", diskId)
   730  		}
   731  		status = disk.Status
   732  		log.Debugf("status %s expect %s", status, DISK_STATUS_IN_USE)
   733  		if status == DISK_STATUS_IN_USE {
   734  			return nil
   735  		}
   736  		time.Sleep(time.Second * 15)
   737  	}
   738  	return fmt.Errorf("timeout for waitting attach disk, current status: %s", status)
   739  }
   740  
   741  func (region *SRegion) MigrateVM(instanceId string, hostName string) error {
   742  	params := jsonutils.NewDict()
   743  	migrate := jsonutils.NewDict()
   744  	migrate.Add(jsonutils.JSONNull, "host")
   745  	if hostName != "" {
   746  		migrate.Add(jsonutils.NewString(hostName), "host")
   747  	}
   748  	params.Add(migrate, "migrate")
   749  	resource := fmt.Sprintf("/servers/%s/action", instanceId)
   750  	_, err := region.ecsPost(resource, params)
   751  	if err != nil {
   752  		return errors.Wrapf(err, "On Requst Migrate instance:%s", instanceId)
   753  	}
   754  	return nil
   755  }
   756  
   757  func (region *SRegion) LiveMigrateVM(instanceId string, hostName string) error {
   758  	params := jsonutils.NewDict()
   759  	osMigrateLive := jsonutils.NewDict()
   760  	osMigrateLive.Add(jsonutils.NewString("auto"), "block_migration")
   761  	osMigrateLive.Add(jsonutils.JSONNull, "host")
   762  	if hostName != "" {
   763  		osMigrateLive.Add(jsonutils.NewString(hostName), "host")
   764  	}
   765  	params.Add(osMigrateLive, "os-migrateLive")
   766  	resource := fmt.Sprintf("/servers/%s/action", instanceId)
   767  	_, err := region.ecsPost(resource, params)
   768  	if err != nil {
   769  		return errors.Wrapf(err, "On Requst LiveMigrate instance:%s", instanceId)
   770  	}
   771  	return nil
   772  }
   773  
   774  //仅live-migration
   775  func (region *SRegion) ListServerMigration(instanceId string) error {
   776  	resource := fmt.Sprintf("/servers/%s/migrations", instanceId)
   777  	_, err := region.ecsGet(resource)
   778  	if err != nil {
   779  		return errors.Wrapf(err, "ListServerMigration")
   780  	}
   781  	return nil
   782  }
   783  
   784  //仅live-migration
   785  func (region *SRegion) DeleteMigration(instanceId string, migrationId string) error {
   786  	resource := fmt.Sprintf("/servers/%s/migrations/%s", instanceId, migrationId)
   787  	_, err := region.ecsDelete(resource)
   788  	if err != nil {
   789  		return errors.Wrapf(err, "On Requst delete LiveMigrate:%s", migrationId)
   790  	}
   791  	return nil
   792  }
   793  
   794  //仅live-migration
   795  func (region *SRegion) ForceCompleteMigration(instanceId string, migrationId string) error {
   796  	params := jsonutils.NewDict()
   797  	params.Add(jsonutils.JSONNull, "force_complete")
   798  	resource := fmt.Sprintf("/servers/%s/migrations/%s/action", instanceId, migrationId)
   799  	_, err := region.ecsPost(resource, params)
   800  	if err != nil {
   801  		return errors.Wrapf(err, "On Requst delete LiveMigrate:%s", migrationId)
   802  	}
   803  	return nil
   804  }
   805  
   806  func (region *SRegion) GetMigrations(instanceId string, migrationType string) (jsonutils.JSONObject, error) {
   807  	query := url.Values{}
   808  	query.Set("instance_uuid", instanceId)
   809  	query.Set("migration_type", migrationType)
   810  	resource := "/os-migrations"
   811  	migrations, err := region.ecsList(resource, query)
   812  	if err != nil {
   813  		return nil, errors.Wrapf(err, "On Get instance :%s Migration,migration_type:%s", instanceId, migrationType)
   814  	}
   815  	return migrations, nil
   816  }
   817  
   818  func (instance *SInstance) AssignSecurityGroup(secgroupId string) error {
   819  	if secgroupId == SECGROUP_NOT_SUPPORT {
   820  		return fmt.Errorf("Security groups are not supported. Security group components are not installed")
   821  	}
   822  	secgroup, err := instance.host.zone.region.GetSecurityGroup(secgroupId)
   823  	if err != nil {
   824  		return errors.Wrapf(err, "GetSecurityGroup(%s)", secgroupId)
   825  	}
   826  	params := map[string]map[string]string{
   827  		"addSecurityGroup": {
   828  			"name": secgroup.Name,
   829  		},
   830  	}
   831  	resource := fmt.Sprintf("/servers/%s/action", instance.Id)
   832  	_, err = instance.host.zone.region.ecsDo(instance.GetProjectId(), resource, params)
   833  	return err
   834  }
   835  
   836  func (instance *SInstance) RevokeSecurityGroup(secgroupId string) error {
   837  	// 若OpenStack不支持安全组,则忽略解绑安全组
   838  	if secgroupId == SECGROUP_NOT_SUPPORT {
   839  		return nil
   840  	}
   841  	secgroup, err := instance.host.zone.region.GetSecurityGroup(secgroupId)
   842  	if err != nil {
   843  		return errors.Wrapf(err, "GetSecurityGroup(%s)", secgroupId)
   844  	}
   845  	params := map[string]map[string]string{
   846  		"removeSecurityGroup": {
   847  			"name": secgroup.Name,
   848  		},
   849  	}
   850  	resource := fmt.Sprintf("/servers/%s/action", instance.Id)
   851  	_, err = instance.host.zone.region.ecsDo(instance.GetProjectId(), resource, params)
   852  	return err
   853  }
   854  
   855  func (instance *SInstance) SetSecurityGroups(secgroupIds []string) error {
   856  	secgroups, err := instance.host.zone.region.GetSecurityGroupsByInstance(instance.Id)
   857  	if err != nil {
   858  		return errors.Wrapf(err, "GetSecurityGroupsByInstance(%s)", instance.Id)
   859  	}
   860  	local := set.New(set.ThreadSafe)
   861  	for _, secgroup := range secgroups {
   862  		local.Add(secgroup.Id)
   863  	}
   864  	newG := set.New(set.ThreadSafe)
   865  	for _, secgroupId := range secgroupIds {
   866  		newG.Add(secgroupId)
   867  	}
   868  	for _, del := range set.Difference(local, newG).List() {
   869  		secgroupId := del.(string)
   870  		err := instance.RevokeSecurityGroup(secgroupId)
   871  		if err != nil {
   872  			return errors.Wrapf(err, "RevokeSecurityGroup(%s)", secgroupId)
   873  		}
   874  	}
   875  	for _, add := range set.Difference(newG, local).List() {
   876  		secgroupId := add.(string)
   877  		err := instance.AssignSecurityGroup(secgroupId)
   878  		if err != nil {
   879  			return errors.Wrapf(err, "AssignSecurityGroup(%s)", secgroupId)
   880  		}
   881  	}
   882  	return nil
   883  }
   884  
   885  func (instance *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
   886  	for networkName, address := range instance.Addresses {
   887  		for i := 0; i < len(address); i++ {
   888  			if instance.Addresses[networkName][i].Type == "floating" {
   889  				return instance.host.zone.region.GetEipByIp(instance.Addresses[networkName][i].Addr)
   890  			}
   891  		}
   892  	}
   893  	return nil, nil
   894  }
   895  
   896  func (instance *SInstance) GetBillingType() string {
   897  	return billing_api.BILLING_TYPE_POSTPAID
   898  }
   899  
   900  func (instance *SInstance) GetCreatedAt() time.Time {
   901  	return instance.Created
   902  }
   903  
   904  func (instance *SInstance) GetExpiredAt() time.Time {
   905  	return time.Time{}
   906  }
   907  
   908  func (instance *SInstance) UpdateUserData(userData string) error {
   909  	return cloudprovider.ErrNotSupported
   910  }
   911  
   912  func (instance *SInstance) Renew(bc billing.SBillingCycle) error {
   913  	return cloudprovider.ErrNotSupported
   914  }
   915  
   916  func (region *SRegion) RenewInstances(instanceId []string, bc billing.SBillingCycle) error {
   917  	return cloudprovider.ErrNotSupported
   918  }
   919  
   920  func (instance *SInstance) GetProjectId() string {
   921  	return instance.TenantId
   922  }
   923  
   924  func (self *SInstance) GetError() error {
   925  	if self.Status == INSTANCE_STATUS_ERROR && len(self.Fault.Message) > 0 {
   926  		return errors.Error(self.Fault.Message)
   927  	}
   928  	return nil
   929  }
   930  
   931  func (instance *SInstance) MigrateVM(hostId string) error {
   932  	hostName := ""
   933  	if hostId != "" {
   934  		iHost, err := instance.host.zone.region.GetIHostById(hostId)
   935  		if err != nil {
   936  			return errors.Wrapf(err, "GetIHostById(%s)", hostId)
   937  		}
   938  		hostName = iHost.GetName()
   939  	}
   940  
   941  	previousHostName := instance.Host
   942  	err := instance.host.zone.region.MigrateVM(instance.Id, hostName)
   943  	if err != nil {
   944  		return errors.Wrap(err, "MigrateVm")
   945  	}
   946  	err = cloudprovider.WaitMultiStatus(instance, []string{api.VM_SYNC_CONFIG, api.VM_READY, api.VM_UNKNOWN}, time.Second*10, time.Hour*3)
   947  	if err != nil {
   948  		return errors.Wrap(err, "WaitMultiStatus")
   949  	}
   950  	if instance.GetStatus() == api.VM_UNKNOWN {
   951  		return errors.Wrap(errors.ErrInvalidStatus, "GetStatus")
   952  	}
   953  	if instance.GetStatus() == api.VM_READY {
   954  		if instance.Host == previousHostName {
   955  			return errors.Wrap(fmt.Errorf("instance not migrated"), "Check host after migration")
   956  		}
   957  		return nil
   958  	}
   959  	return instance.host.zone.region.instanceOperation(instance.Id, "confirmResize")
   960  }
   961  
   962  func (instance *SInstance) LiveMigrateVM(hostId string) error {
   963  	hostName := ""
   964  	if hostId != "" {
   965  		iHost, err := instance.host.zone.region.GetIHostById(hostId)
   966  		if err != nil {
   967  			return errors.Wrapf(err, "GetIHostById(%s)", hostId)
   968  		}
   969  		hostName = iHost.GetName()
   970  	}
   971  	previousHostName := instance.Host
   972  	err := instance.host.zone.region.LiveMigrateVM(instance.Id, hostName)
   973  	if err != nil {
   974  		return errors.Wrap(err, "LiveMIgrateVm")
   975  	}
   976  	err = cloudprovider.WaitMultiStatus(instance, []string{api.VM_SYNC_CONFIG, api.VM_RUNNING, api.VM_UNKNOWN}, time.Second*10, time.Hour*3)
   977  	if err != nil {
   978  		return errors.Wrap(err, "WaitMultiStatus")
   979  	}
   980  	if instance.GetStatus() == api.VM_UNKNOWN {
   981  		return errors.Wrap(errors.ErrInvalidStatus, "GetStatus")
   982  	}
   983  	if instance.GetStatus() == api.VM_RUNNING {
   984  		if instance.Host == previousHostName {
   985  			return errors.Wrap(fmt.Errorf("instance not migrated"), "Check host after migration")
   986  		}
   987  		return nil
   988  	}
   989  	return instance.host.zone.region.instanceOperation(instance.Id, "confirmResize")
   990  }
   991  func (instance *SInstance) GetIHostId() string {
   992  	err := instance.host.zone.fetchHosts()
   993  	if err != nil {
   994  		return ""
   995  	}
   996  	for _, host := range instance.host.zone.hosts {
   997  		if instance.HypervisorHostname == host.HypervisorHostname {
   998  			return host.GetGlobalId()
   999  		}
  1000  	}
  1001  	return ""
  1002  }
  1003  
  1004  func (region *SRegion) GetInstanceMetadata(instanceId string) (map[string]string, error) {
  1005  	resource := fmt.Sprintf("/servers/%s/metadata", instanceId)
  1006  	resp, err := region.ecsList(resource, nil)
  1007  	if err != nil {
  1008  		return nil, errors.Wrap(err, "ecsList")
  1009  	}
  1010  	result := struct {
  1011  		Metadata map[string]string
  1012  	}{}
  1013  	err = resp.Unmarshal(&result)
  1014  	if err != nil {
  1015  		return nil, errors.Wrap(err, "resp.Unmarshal")
  1016  	}
  1017  	return result.Metadata, nil
  1018  }
  1019  
  1020  func (zone *SZone) CreateVM(hypervisor string, opts *cloudprovider.SManagedVMCreateConfig) (*SInstance, error) {
  1021  	region := zone.region
  1022  	network, err := region.GetNetwork(opts.ExternalNetworkId)
  1023  	if err != nil {
  1024  		return nil, err
  1025  	}
  1026  
  1027  	secgroups := []map[string]string{}
  1028  	for _, secgroupId := range opts.ExternalSecgroupIds {
  1029  		if secgroupId != SECGROUP_NOT_SUPPORT {
  1030  			secgroups = append(secgroups, map[string]string{"name": secgroupId})
  1031  		}
  1032  	}
  1033  
  1034  	image, err := region.GetImage(opts.ExternalImageId)
  1035  	if err != nil {
  1036  		return nil, errors.Wrapf(err, "GetImage(%s)", opts.ExternalImageId)
  1037  	}
  1038  
  1039  	sysDiskSizeGB := image.Size / 1024 / 1024 / 1024
  1040  	if opts.SysDisk.SizeGB < sysDiskSizeGB {
  1041  		opts.SysDisk.SizeGB = sysDiskSizeGB
  1042  	}
  1043  
  1044  	if opts.SysDisk.SizeGB < image.GetMinOsDiskSizeGb() {
  1045  		opts.SysDisk.SizeGB = image.GetMinOsDiskSizeGb()
  1046  	}
  1047  
  1048  	BlockDeviceMappingV2 := []map[string]interface{}{}
  1049  
  1050  	diskIds := []string{}
  1051  
  1052  	defer func() {
  1053  		for _, diskId := range diskIds {
  1054  			err = region.DeleteDisk(diskId)
  1055  			if err != nil {
  1056  				log.Errorf("clean disk %s error: %v", diskId, err)
  1057  			}
  1058  		}
  1059  	}()
  1060  
  1061  	if opts.SysDisk.StorageType != api.STORAGE_OPENSTACK_NOVA { //新建volume
  1062  		istorage, err := zone.GetIStorageById(opts.SysDisk.StorageExternalId)
  1063  		if err != nil {
  1064  			return nil, errors.Wrapf(err, "GetIStorageById(%s)", opts.SysDisk.StorageExternalId)
  1065  		}
  1066  
  1067  		_sysDisk, err := region.CreateDisk(opts.ExternalImageId, istorage.GetName(), "", opts.SysDisk.SizeGB, opts.SysDisk.Name, opts.ProjectId)
  1068  		if err != nil {
  1069  			return nil, errors.Wrapf(err, "CreateDisk %s", opts.SysDisk.Name)
  1070  		}
  1071  
  1072  		diskIds = append(diskIds, _sysDisk.GetGlobalId())
  1073  
  1074  		BlockDeviceMappingV2 = append(BlockDeviceMappingV2, map[string]interface{}{
  1075  			"boot_index":            0,
  1076  			"uuid":                  _sysDisk.GetGlobalId(),
  1077  			"source_type":           "volume",
  1078  			"destination_type":      "volume",
  1079  			"delete_on_termination": true,
  1080  		})
  1081  	} else {
  1082  		BlockDeviceMappingV2 = append(BlockDeviceMappingV2, map[string]interface{}{
  1083  			"boot_index":            0,
  1084  			"uuid":                  image.Id,
  1085  			"source_type":           "image",
  1086  			"destination_type":      "local",
  1087  			"delete_on_termination": true,
  1088  		})
  1089  	}
  1090  
  1091  	var _disk *SDisk
  1092  	for index, disk := range opts.DataDisks {
  1093  		istorage, err := zone.GetIStorageById(disk.StorageExternalId)
  1094  		if err != nil {
  1095  			return nil, errors.Wrapf(err, "GetIStorageById(%s)", disk.StorageExternalId)
  1096  		}
  1097  		_disk, err = region.CreateDisk("", istorage.GetName(), "", disk.SizeGB, disk.Name, opts.ProjectId)
  1098  		if err != nil {
  1099  			return nil, errors.Wrapf(err, "CreateDisk %s", disk.Name)
  1100  		}
  1101  		diskIds = append(diskIds, _disk.Id)
  1102  
  1103  		mapping := map[string]interface{}{
  1104  			"source_type":           "volume",
  1105  			"destination_type":      "volume",
  1106  			"delete_on_termination": true,
  1107  			"boot_index":            index + 1,
  1108  			"uuid":                  _disk.Id,
  1109  		}
  1110  
  1111  		BlockDeviceMappingV2 = append(BlockDeviceMappingV2, mapping)
  1112  	}
  1113  
  1114  	az := zone.ZoneName
  1115  	if len(hypervisor) > 0 {
  1116  		az = fmt.Sprintf("%s:%s", zone.ZoneName, hypervisor)
  1117  	}
  1118  
  1119  	net := map[string]string{
  1120  		"uuid": network.NetworkId,
  1121  	}
  1122  	if len(opts.IpAddr) > 0 {
  1123  		net["fixed_ip"] = opts.IpAddr
  1124  	}
  1125  
  1126  	params := map[string]map[string]interface{}{
  1127  		"server": {
  1128  			"name":                    opts.Name,
  1129  			"adminPass":               opts.Password,
  1130  			"availability_zone":       az,
  1131  			"networks":                []map[string]string{net},
  1132  			"security_groups":         secgroups,
  1133  			"user_data":               opts.UserData,
  1134  			"imageRef":                opts.ExternalImageId,
  1135  			"block_device_mapping_v2": BlockDeviceMappingV2,
  1136  		},
  1137  	}
  1138  	if len(opts.IpAddr) > 0 {
  1139  		params["server"]["accessIPv4"] = opts.IpAddr
  1140  	}
  1141  
  1142  	flavor, err := region.syncFlavor(opts.InstanceType, opts.Cpu, opts.MemoryMB, opts.SysDisk.SizeGB)
  1143  	if err != nil {
  1144  		return nil, err
  1145  	}
  1146  	params["server"]["flavorRef"] = flavor.Id
  1147  
  1148  	if len(opts.PublicKey) > 0 {
  1149  		keypairName, err := region.syncKeypair(opts.Name, opts.PublicKey)
  1150  		if err != nil {
  1151  			return nil, err
  1152  		}
  1153  		params["server"]["key_name"] = keypairName
  1154  	}
  1155  
  1156  	resp, err := region.ecsCreate(opts.ProjectId, "/servers", params)
  1157  	if err != nil {
  1158  		return nil, errors.Wrap(err, "ecsCreate")
  1159  	}
  1160  	diskIds = []string{}
  1161  	instance := &SInstance{}
  1162  	err = resp.Unmarshal(instance, "server")
  1163  	if err != nil {
  1164  		return nil, errors.Wrap(err, "resp.Unmarshal")
  1165  	}
  1166  	return instance, nil
  1167  }