yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/google/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 google
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"strings"
    21  	"time"
    22  
    23  	"yunion.io/x/jsonutils"
    24  	"yunion.io/x/log"
    25  	"yunion.io/x/pkg/errors"
    26  	"yunion.io/x/pkg/util/fileutils"
    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  	"yunion.io/x/onecloud/pkg/util/cloudinit"
    35  	"yunion.io/x/onecloud/pkg/util/encode"
    36  	"yunion.io/x/onecloud/pkg/util/imagetools"
    37  	"yunion.io/x/onecloud/pkg/util/pinyinutils"
    38  )
    39  
    40  const (
    41  	METADATA_SSH_KEYS                   = "ssh-keys"
    42  	METADATA_STARTUP_SCRIPT             = "startup-script"
    43  	METADATA_POWER_SHELL                = "sysprep-specialize-script-ps1"
    44  	METADATA_STARTUP_SCRIPT_POWER_SHELL = "windows-startup-script-ps1"
    45  )
    46  
    47  type AccessConfig struct {
    48  	Type        string
    49  	Name        string
    50  	NatIP       string
    51  	NetworkTier string
    52  	Kind        string
    53  }
    54  
    55  type InstanceDisk struct {
    56  	Type            string
    57  	Mode            string
    58  	Source          string
    59  	DeviceName      string
    60  	Index           int
    61  	Boot            bool
    62  	AutoDelete      bool
    63  	Licenses        []string
    64  	Interface       string
    65  	GuestOsFeatures []GuestOsFeature
    66  	Kind            string
    67  }
    68  
    69  type ServiceAccount struct {
    70  	Email  string
    71  	scopes []string
    72  }
    73  
    74  type SInstanceTag struct {
    75  	Items       []string
    76  	Fingerprint string
    77  }
    78  
    79  type SMetadataItem struct {
    80  	Key   string
    81  	Value string
    82  }
    83  
    84  type SMetadata struct {
    85  	Fingerprint string
    86  	Items       []SMetadataItem
    87  }
    88  
    89  type SInstance struct {
    90  	multicloud.SInstanceBase
    91  	GoogleTags
    92  	host *SHost
    93  	SResourceBase
    94  
    95  	osInfo *imagetools.ImageInfo
    96  
    97  	CreationTimestamp  time.Time
    98  	Description        string
    99  	Tags               SInstanceTag
   100  	MachineType        string
   101  	Status             string
   102  	Zone               string
   103  	CanIpForward       bool
   104  	NetworkInterfaces  []SNetworkInterface
   105  	Disks              []InstanceDisk
   106  	Metadata           SMetadata
   107  	ServiceAccounts    []ServiceAccount
   108  	Scheduling         map[string]interface{}
   109  	CpuPlatform        string
   110  	LabelFingerprint   string
   111  	StartRestricted    bool
   112  	DeletionProtection bool
   113  	Kind               string
   114  
   115  	guestCpus   int
   116  	memoryMb    int
   117  	machineType string
   118  }
   119  
   120  func (region *SRegion) GetInstances(zone string, maxResults int, pageToken string) ([]SInstance, error) {
   121  	instances := []SInstance{}
   122  	params := map[string]string{}
   123  	if len(zone) == 0 {
   124  		return nil, fmt.Errorf("zone params can not be empty")
   125  	}
   126  	resource := fmt.Sprintf("zones/%s/instances", zone)
   127  	return instances, region.List(resource, params, maxResults, pageToken, &instances)
   128  }
   129  
   130  func (region *SRegion) GetInstance(id string) (*SInstance, error) {
   131  	instance := &SInstance{}
   132  	return instance, region.Get("instances", id, instance)
   133  }
   134  
   135  func (instance *SInstance) GetHostname() string {
   136  	return instance.GetName()
   137  }
   138  
   139  func (instance *SInstance) fetchMachineType() error {
   140  	if instance.guestCpus > 0 || instance.memoryMb > 0 || len(instance.machineType) > 0 {
   141  		return nil
   142  	}
   143  	machinetype := SMachineType{}
   144  	err := instance.host.zone.region.GetBySelfId(instance.MachineType, &machinetype)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	instance.guestCpus = machinetype.GuestCpus
   149  	instance.memoryMb = machinetype.MemoryMb
   150  	instance.machineType = machinetype.Name
   151  	return nil
   152  }
   153  
   154  func (self *SInstance) Refresh() error {
   155  	instance, err := self.host.zone.region.GetInstance(self.Id)
   156  	if err != nil {
   157  		return err
   158  	}
   159  	err = jsonutils.Update(self, instance)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	instance.Labels = self.Labels
   164  	return nil
   165  }
   166  
   167  //PROVISIONING, STAGING, RUNNING, STOPPING, STOPPED, SUSPENDING, SUSPENDED, and TERMINATED.
   168  func (instance *SInstance) GetStatus() string {
   169  	switch instance.Status {
   170  	case "PROVISIONING":
   171  		return api.VM_DEPLOYING
   172  	case "STAGING":
   173  		return api.VM_STARTING
   174  	case "RUNNING":
   175  		return api.VM_RUNNING
   176  	case "STOPPING":
   177  		return api.VM_STOPPING
   178  	case "STOPPED":
   179  		return api.VM_READY
   180  	case "SUSPENDING":
   181  		return api.VM_SUSPENDING
   182  	case "SUSPENDED":
   183  		return api.VM_SUSPEND
   184  	case "TERMINATED":
   185  		return api.VM_READY
   186  	default:
   187  		return api.VM_UNKNOWN
   188  	}
   189  }
   190  
   191  func (instance *SInstance) GetBillingType() string {
   192  	return billing_api.BILLING_TYPE_POSTPAID
   193  }
   194  
   195  func (instance *SInstance) GetCreatedAt() time.Time {
   196  	return instance.CreationTimestamp
   197  }
   198  
   199  func (instance *SInstance) GetExpiredAt() time.Time {
   200  	return time.Time{}
   201  }
   202  
   203  func (instance *SInstance) GetProjectId() string {
   204  	return instance.host.zone.region.GetProjectId()
   205  }
   206  
   207  func (instance *SInstance) GetIHost() cloudprovider.ICloudHost {
   208  	return instance.host
   209  }
   210  
   211  func (instance *SInstance) GetIHostId() string {
   212  	return instance.host.GetGlobalId()
   213  }
   214  
   215  func (instance *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
   216  	idisks := []cloudprovider.ICloudDisk{}
   217  	for _, disk := range instance.Disks {
   218  		_disk := &SDisk{}
   219  		err := instance.host.zone.region.GetBySelfId(disk.Source, _disk)
   220  		if err != nil {
   221  			return nil, errors.Wrap(err, "GetDisk")
   222  		}
   223  		storage, err := instance.host.zone.region.GetStorage(_disk.Type)
   224  		if err != nil {
   225  			return nil, errors.Wrap(err, "GetStorage")
   226  		}
   227  		storage.zone = instance.host.zone
   228  		_disk.storage = storage
   229  		_disk.autoDelete = disk.AutoDelete
   230  		_disk.boot = disk.Boot
   231  		_disk.index = disk.Index
   232  		idisks = append(idisks, _disk)
   233  	}
   234  	return idisks, nil
   235  }
   236  
   237  func (instance *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
   238  	nics := []cloudprovider.ICloudNic{}
   239  	for i := range instance.NetworkInterfaces {
   240  		instance.NetworkInterfaces[i].instance = instance
   241  		nics = append(nics, &instance.NetworkInterfaces[i])
   242  	}
   243  	return nics, nil
   244  }
   245  
   246  func (instance *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
   247  	for _, networkinterface := range instance.NetworkInterfaces {
   248  		for _, conf := range networkinterface.AccessConfigs {
   249  			if len(conf.NatIP) > 0 {
   250  				eips, err := instance.host.zone.region.GetEips(conf.NatIP, 0, "")
   251  				if err != nil {
   252  					return nil, errors.Wrapf(err, "region.GetEip(%s)", conf.NatIP)
   253  				}
   254  				if len(eips) == 1 {
   255  					eips[0].region = instance.host.zone.region
   256  					return &eips[0], nil
   257  				}
   258  				eip := &SAddress{
   259  					region:     instance.host.zone.region,
   260  					Status:     "IN_USE",
   261  					Address:    conf.NatIP,
   262  					instanceId: instance.Id,
   263  				}
   264  				eip.Id = instance.Id
   265  				eip.SelfLink = instance.SelfLink
   266  				return eip, nil
   267  			}
   268  		}
   269  	}
   270  	return nil, nil
   271  }
   272  
   273  func (instance *SInstance) GetVcpuCount() int {
   274  	instance.fetchMachineType()
   275  	return instance.guestCpus
   276  }
   277  
   278  func (instance *SInstance) GetVmemSizeMB() int {
   279  	instance.fetchMachineType()
   280  	return instance.memoryMb
   281  }
   282  
   283  func (instance *SInstance) GetBootOrder() string {
   284  	return "cdn"
   285  }
   286  
   287  func (instance *SInstance) GetVga() string {
   288  	return "std"
   289  }
   290  
   291  func (instance *SInstance) GetVdi() string {
   292  	return "vnc"
   293  }
   294  
   295  func (instance *SInstance) GetOsType() cloudprovider.TOsType {
   296  	return cloudprovider.TOsType(instance.getNormalizedOsInfo().OsType)
   297  }
   298  
   299  func (instance *SInstance) getValidLicense() string {
   300  	for _, disk := range instance.Disks {
   301  		if disk.Index == 0 {
   302  			for _, license := range disk.Licenses {
   303  				if len(license) > 0 {
   304  					return license
   305  				}
   306  			}
   307  		}
   308  	}
   309  	return ""
   310  }
   311  
   312  func (instance *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo {
   313  	if instance.osInfo != nil {
   314  		return instance.osInfo
   315  	}
   316  	osinfo := imagetools.NormalizeImageInfo(instance.getValidLicense(), "", "", "", "")
   317  	instance.osInfo = &osinfo
   318  	return instance.osInfo
   319  }
   320  
   321  func (instance *SInstance) GetFullOsName() string {
   322  	return instance.getValidLicense()
   323  }
   324  
   325  func (instance *SInstance) GetBios() cloudprovider.TBiosType {
   326  	return cloudprovider.ToBiosType(instance.getNormalizedOsInfo().OsBios)
   327  }
   328  
   329  func (instance *SInstance) GetOsArch() string {
   330  	return instance.getNormalizedOsInfo().OsArch
   331  }
   332  
   333  func (instance *SInstance) GetOsDist() string {
   334  	return instance.getNormalizedOsInfo().OsDistro
   335  }
   336  
   337  func (instance *SInstance) GetOsVersion() string {
   338  	return instance.getNormalizedOsInfo().OsVersion
   339  }
   340  
   341  func (instance *SInstance) GetOsLang() string {
   342  	return instance.getNormalizedOsInfo().OsLang
   343  }
   344  
   345  func (instance *SInstance) GetMachine() string {
   346  	return "pc"
   347  }
   348  
   349  func (instance *SInstance) GetInstanceType() string {
   350  	instance.fetchMachineType()
   351  	return instance.machineType
   352  }
   353  
   354  func (instance *SInstance) AssignSecurityGroup(id string) error {
   355  	for _, secgrpType := range []string{SECGROUP_TYPE_TAG, SECGROUP_TYPE_SERVICE_ACCOUNT} {
   356  		if strings.Contains(id, fmt.Sprintf("/%s/", secgrpType)) {
   357  			idx := strings.LastIndex(id, "/") + 1
   358  			if idx <= 0 {
   359  				return fmt.Errorf("invalid secgroup %s with id %s", secgrpType, id)
   360  			}
   361  			secgroup := id[idx:]
   362  			switch secgrpType {
   363  			case SECGROUP_TYPE_TAG:
   364  				tag := strings.ToLower(secgroup)
   365  				if !utils.IsInStringArray(tag, instance.Tags.Items) {
   366  					instance.Tags.Items = append(instance.Tags.Items, tag)
   367  					return instance.host.zone.region.SetResourceTags(instance.SelfLink, instance.Tags)
   368  				}
   369  			case SECGROUP_TYPE_SERVICE_ACCOUNT:
   370  				if len(instance.ServiceAccounts) > 0 {
   371  					return fmt.Errorf("instance %s has already set serviceAccount %s", instance.Name, instance.ServiceAccounts[0].Email)
   372  				}
   373  				return instance.host.zone.region.SetServiceAccount(instance.SelfLink, secgroup)
   374  			}
   375  		}
   376  	}
   377  	return fmt.Errorf("unknown secgroup type %s", id)
   378  }
   379  
   380  func (instance *SInstance) GetSecurityGroupIds() ([]string, error) {
   381  	secgroupIds := []string{}
   382  	isecgroups := []cloudprovider.ICloudSecurityGroup{}
   383  	for _, networkinterface := range instance.NetworkInterfaces {
   384  		vpc := SVpc{region: instance.host.zone.region}
   385  		err := instance.host.zone.region.GetBySelfId(networkinterface.Subnetwork, &vpc)
   386  		if err != nil {
   387  			return nil, errors.Wrap(err, "GetGlobalNetwork")
   388  		}
   389  		_isecgroups, err := vpc.GetISecurityGroups()
   390  		if err != nil {
   391  			return nil, errors.Wrap(err, "vpc.GetISecurityGroups")
   392  		}
   393  		isecgroups = append(isecgroups, _isecgroups...)
   394  		for _, isecgroup := range _isecgroups {
   395  			if len(instance.ServiceAccounts) > 0 && isecgroup.GetName() == instance.ServiceAccounts[0].Email {
   396  				secgroupIds = append(secgroupIds, isecgroup.GetGlobalId())
   397  			}
   398  			gvpcInfo := strings.Split(vpc.Network, "/")
   399  			gvpcName := gvpcInfo[len(gvpcInfo)-1]
   400  			if isecgroup.GetName() == gvpcName && !strings.Contains(isecgroup.GetGlobalId(), fmt.Sprintf("/%s/", SECGROUP_TYPE_TAG)) {
   401  				secgroupIds = append(secgroupIds, isecgroup.GetGlobalId())
   402  			}
   403  		}
   404  	}
   405  	if len(instance.NetworkInterfaces) == 1 {
   406  		for _, secgroup := range isecgroups {
   407  			if utils.IsInStringArray(secgroup.GetName(), instance.Tags.Items) && strings.Contains(secgroup.GetGlobalId(), fmt.Sprintf("/%s/", SECGROUP_TYPE_TAG)) {
   408  				secgroupIds = append(secgroupIds, secgroup.GetGlobalId())
   409  			}
   410  		}
   411  	}
   412  	return secgroupIds, nil
   413  }
   414  
   415  func (instance *SInstance) SetSecurityGroups(ids []string) error {
   416  	secgroups := map[string][]string{}
   417  	for _, id := range ids {
   418  		for _, secgrpType := range []string{SECGROUP_TYPE_TAG, SECGROUP_TYPE_SERVICE_ACCOUNT} {
   419  			if strings.Contains(id, fmt.Sprintf("/%s/", secgrpType)) {
   420  				idx := strings.LastIndex(id, "/") + 1
   421  				if idx <= 0 {
   422  					return fmt.Errorf("invalid secgroup %s with id %s", secgrpType, id)
   423  				}
   424  				secgroup := id[idx:]
   425  				if len(secgroup) == 0 {
   426  					return fmt.Errorf("invalid secgroup %s with id %s", secgrpType, id)
   427  				}
   428  				if _, ok := secgroups[secgrpType]; !ok {
   429  					secgroups[secgrpType] = []string{}
   430  				}
   431  				if !utils.IsInStringArray(secgroup, secgroups[secgrpType]) {
   432  					secgroups[secgrpType] = append(secgroups[secgrpType], secgroup)
   433  				}
   434  			}
   435  		}
   436  	}
   437  	if tags, ok := secgroups[SECGROUP_TYPE_TAG]; ok && len(tags) > 0 {
   438  		for _, tag := range tags {
   439  			tag = strings.ToLower(tag)
   440  			if !utils.IsInStringArray(tag, instance.Tags.Items) {
   441  				instance.Tags.Items = append(instance.Tags.Items, tag)
   442  			}
   443  		}
   444  		err := instance.host.zone.region.SetResourceTags(instance.SelfLink, instance.Tags)
   445  		if err != nil {
   446  			return errors.Wrap(err, "SetTags")
   447  		}
   448  	}
   449  	if serviceAccounts, ok := secgroups[SECGROUP_TYPE_SERVICE_ACCOUNT]; ok && len(serviceAccounts) > 0 {
   450  		if len(serviceAccounts) > 1 {
   451  			return fmt.Errorf("can not set multi service account for google instance")
   452  		}
   453  		return instance.host.zone.region.SetServiceAccount(instance.SelfLink, serviceAccounts[0])
   454  	}
   455  	return nil
   456  }
   457  
   458  func (instance *SInstance) GetHypervisor() string {
   459  	return api.HYPERVISOR_GOOGLE
   460  }
   461  
   462  func (instance *SInstance) StartVM(ctx context.Context) error {
   463  	return instance.host.zone.region.StartInstance(instance.SelfLink)
   464  }
   465  
   466  func (instance *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
   467  	return instance.host.zone.region.StopInstance(instance.SelfLink)
   468  }
   469  
   470  func (instance *SInstance) DeleteVM(ctx context.Context) error {
   471  	return instance.host.zone.region.Delete(instance.SelfLink)
   472  }
   473  
   474  func (instance *SInstance) UpdateVM(ctx context.Context, name string) error {
   475  	return cloudprovider.ErrNotSupported
   476  }
   477  
   478  func (instance *SInstance) UpdateUserData(userData string) error {
   479  	items := []SMetadataItem{}
   480  	for _, item := range instance.Metadata.Items {
   481  		if item.Key != METADATA_STARTUP_SCRIPT && item.Key != METADATA_POWER_SHELL && item.Key != METADATA_STARTUP_SCRIPT_POWER_SHELL {
   482  			items = append(items, item)
   483  		}
   484  	}
   485  	if len(userData) > 0 {
   486  		items = append(items, SMetadataItem{Key: METADATA_STARTUP_SCRIPT, Value: userData})
   487  		items = append(items, SMetadataItem{Key: METADATA_STARTUP_SCRIPT_POWER_SHELL, Value: userData})
   488  		items = append(items, SMetadataItem{Key: METADATA_POWER_SHELL, Value: userData})
   489  	}
   490  	instance.Metadata.Items = items
   491  	return instance.host.zone.region.SetMetadata(instance.SelfLink, instance.Metadata)
   492  }
   493  
   494  func (instance *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
   495  	diskId, err := instance.host.zone.region.RebuildRoot(instance.Id, desc.ImageId, desc.SysSizeGB)
   496  	if err != nil {
   497  		return "", errors.Wrap(err, "region.RebuildRoot")
   498  	}
   499  	return diskId, instance.DeployVM(ctx, "", desc.Account, desc.Password, desc.PublicKey, false, "")
   500  }
   501  
   502  func (instance *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error {
   503  	conf := cloudinit.SCloudConfig{}
   504  	user := cloudinit.NewUser(username)
   505  	if len(password) > 0 {
   506  		user.Password(password)
   507  	}
   508  	if len(publicKey) > 0 {
   509  		user.SshKey(publicKey)
   510  	}
   511  	if len(password) > 0 || len(publicKey) > 0 {
   512  		conf.MergeUser(user)
   513  		items := []SMetadataItem{}
   514  		instance.Refresh()
   515  		for _, item := range instance.Metadata.Items {
   516  			if item.Key != METADATA_STARTUP_SCRIPT_POWER_SHELL && item.Key != METADATA_STARTUP_SCRIPT {
   517  				items = append(items, item)
   518  			}
   519  		}
   520  		items = append(items, SMetadataItem{Key: METADATA_STARTUP_SCRIPT_POWER_SHELL, Value: conf.UserDataPowerShell()})
   521  		items = append(items, SMetadataItem{Key: METADATA_STARTUP_SCRIPT, Value: conf.UserDataScript()})
   522  		instance.Metadata.Items = items
   523  		return instance.host.zone.region.SetMetadata(instance.SelfLink, instance.Metadata)
   524  	}
   525  	return nil
   526  }
   527  
   528  func (instance *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
   529  	return instance.host.zone.region.ChangeInstanceConfig(instance.SelfLink, instance.host.zone.Name, config.InstanceType, config.Cpu, config.MemoryMB)
   530  }
   531  
   532  func (instance *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
   533  	return nil, cloudprovider.ErrNotImplemented
   534  }
   535  
   536  func (instance *SInstance) AttachDisk(ctx context.Context, diskId string) error {
   537  	return instance.host.zone.region.AttachDisk(instance.SelfLink, diskId, false)
   538  }
   539  
   540  func (instance *SInstance) DetachDisk(ctx context.Context, diskId string) error {
   541  	_disk, err := instance.host.zone.region.GetDisk(diskId)
   542  	if err != nil {
   543  		if errors.Cause(err) == cloudprovider.ErrNotFound {
   544  			return nil
   545  		}
   546  		return errors.Wrapf(err, "GetDisk(%s)", diskId)
   547  	}
   548  	for _, disk := range instance.Disks {
   549  		if disk.Source == _disk.SelfLink {
   550  			return instance.host.zone.region.DetachDisk(instance.SelfLink, disk.DeviceName)
   551  		}
   552  	}
   553  	return nil
   554  }
   555  
   556  func (instance *SInstance) Renew(bc billing.SBillingCycle) error {
   557  	return cloudprovider.ErrNotSupported
   558  }
   559  
   560  func (instance *SInstance) GetError() error {
   561  	return nil
   562  }
   563  
   564  func getDiskInfo(disk string) (cloudprovider.SDiskInfo, error) {
   565  	result := cloudprovider.SDiskInfo{}
   566  	diskInfo := strings.Split(disk, ":")
   567  	for _, d := range diskInfo {
   568  		if utils.IsInStringArray(d, []string{api.STORAGE_GOOGLE_PD_STANDARD, api.STORAGE_GOOGLE_PD_SSD, api.STORAGE_GOOGLE_LOCAL_SSD, api.STORAGE_GOOGLE_PD_BALANCED}) {
   569  			result.StorageType = d
   570  		} else if memSize, err := fileutils.GetSizeMb(d, 'M', 1024); err == nil {
   571  			result.SizeGB = memSize >> 10
   572  		} else {
   573  			result.Name = d
   574  		}
   575  	}
   576  	if len(result.StorageType) == 0 {
   577  		result.StorageType = api.STORAGE_GOOGLE_PD_STANDARD
   578  	}
   579  
   580  	if result.SizeGB == 0 {
   581  		return result, fmt.Errorf("Missing disk size")
   582  	}
   583  	return result, nil
   584  }
   585  
   586  func (region *SRegion) CreateInstance(zone, name, desc, instanceType string, cpu, memoryMb int, networkId string, ipAddr, imageId string, disks []string) (*SInstance, error) {
   587  	if len(instanceType) == 0 && (cpu == 0 || memoryMb == 0) {
   588  		return nil, fmt.Errorf("Missing instanceType or cpu &memory info")
   589  	}
   590  	if len(disks) == 0 {
   591  		return nil, fmt.Errorf("Missing disk info")
   592  	}
   593  	sysDisk, err := getDiskInfo(disks[0])
   594  	if err != nil {
   595  		return nil, errors.Wrap(err, "getDiskInfo.sys")
   596  	}
   597  	dataDisks := []cloudprovider.SDiskInfo{}
   598  	for _, d := range disks[1:] {
   599  		dataDisk, err := getDiskInfo(d)
   600  		if err != nil {
   601  			return nil, errors.Wrapf(err, "getDiskInfo(%s)", d)
   602  		}
   603  		dataDisks = append(dataDisks, dataDisk)
   604  	}
   605  	conf := &cloudprovider.SManagedVMCreateConfig{
   606  		Name:              name,
   607  		Description:       desc,
   608  		ExternalImageId:   imageId,
   609  		Cpu:               cpu,
   610  		MemoryMB:          memoryMb,
   611  		ExternalNetworkId: networkId,
   612  		IpAddr:            ipAddr,
   613  		SysDisk:           sysDisk,
   614  		DataDisks:         dataDisks,
   615  	}
   616  	return region._createVM(zone, conf)
   617  }
   618  
   619  func (region *SRegion) getSecgroupByIds(ids []string) (map[string][]string, error) {
   620  	secgroups := map[string][]string{}
   621  	for _, id := range ids {
   622  		for _, secgrpType := range []string{SECGROUP_TYPE_TAG, SECGROUP_TYPE_SERVICE_ACCOUNT} {
   623  			if strings.Contains(id, fmt.Sprintf("/%s/", secgrpType)) {
   624  				idx := strings.LastIndex(id, "/") + 1
   625  				if idx <= 0 {
   626  					return nil, fmt.Errorf("invalid secgroup %s for %s", id, secgrpType)
   627  				}
   628  				secgroup := id[idx:]
   629  				if len(secgroup) == 0 {
   630  					return nil, fmt.Errorf("invalid secgroup %s for %s", id, secgrpType)
   631  				}
   632  				if _, ok := secgroups[secgrpType]; !ok {
   633  					secgroups[secgrpType] = []string{}
   634  				}
   635  				if !utils.IsInStringArray(secgroup, secgroups[secgrpType]) {
   636  					secgroups[secgrpType] = append(secgroups[secgrpType], secgroup)
   637  				}
   638  			}
   639  		}
   640  	}
   641  	return secgroups, nil
   642  }
   643  
   644  func (region *SRegion) _createVM(zone string, desc *cloudprovider.SManagedVMCreateConfig) (*SInstance, error) {
   645  	vpc, err := region.GetVpc(desc.ExternalNetworkId)
   646  	if err != nil {
   647  		return nil, errors.Wrap(err, "region.GetNetwork")
   648  	}
   649  	secgroups, err := region.getSecgroupByIds(desc.ExternalSecgroupIds)
   650  	if err != nil {
   651  		return nil, errors.Wrap(err, "getSecgroupByIds")
   652  	}
   653  	serviceAccounts, ok := secgroups[SECGROUP_TYPE_SERVICE_ACCOUNT]
   654  	if ok && len(serviceAccounts) > 1 {
   655  		return nil, fmt.Errorf("Security groups are distributed across multiple service accounts")
   656  	}
   657  	if len(desc.InstanceType) == 0 {
   658  		desc.InstanceType = fmt.Sprintf("custom-%d-%d", desc.Cpu, desc.MemoryMB)
   659  	}
   660  	disks := []map[string]interface{}{}
   661  	if len(desc.SysDisk.Name) == 0 {
   662  		desc.SysDisk.Name = fmt.Sprintf("vdisk-%s-%d", desc.Name, time.Now().UnixNano())
   663  	}
   664  	nameConv := func(name string) string {
   665  		name = strings.Replace(name, "_", "-", -1)
   666  		name = pinyinutils.Text2Pinyin(name)
   667  		return strings.ToLower(name)
   668  	}
   669  	disks = append(disks, map[string]interface{}{
   670  		"boot": true,
   671  		"initializeParams": map[string]interface{}{
   672  			"diskName":    nameConv(desc.SysDisk.Name),
   673  			"sourceImage": desc.ExternalImageId,
   674  			"diskSizeGb":  desc.SysDisk.SizeGB,
   675  			"diskType":    fmt.Sprintf("zones/%s/diskTypes/%s", zone, desc.SysDisk.StorageType),
   676  		},
   677  		"autoDelete": true,
   678  	})
   679  	for _, disk := range desc.DataDisks {
   680  		if len(disk.Name) == 0 {
   681  			disk.Name = fmt.Sprintf("vdisk-%s-%d", desc.Name, time.Now().UnixNano())
   682  		}
   683  		disks = append(disks, map[string]interface{}{
   684  			"boot": false,
   685  			"initializeParams": map[string]interface{}{
   686  				"diskName":   nameConv(disk.Name),
   687  				"diskSizeGb": disk.SizeGB,
   688  				"diskType":   fmt.Sprintf("zones/%s/diskTypes/%s", zone, disk.StorageType),
   689  			},
   690  			"autoDelete": true,
   691  		})
   692  	}
   693  	networkInterface := map[string]string{
   694  		"network":    vpc.Network,
   695  		"subnetwork": vpc.SelfLink,
   696  	}
   697  	if len(desc.IpAddr) > 0 {
   698  		networkInterface["networkIp"] = desc.IpAddr
   699  	}
   700  	params := map[string]interface{}{
   701  		"name":        desc.NameEn,
   702  		"description": desc.Description,
   703  		"machineType": fmt.Sprintf("zones/%s/machineTypes/%s", zone, desc.InstanceType),
   704  		"networkInterfaces": []map[string]string{
   705  			networkInterface,
   706  		},
   707  		"disks": disks,
   708  	}
   709  
   710  	labels := map[string]string{}
   711  	for k, v := range desc.Tags {
   712  		labels[encode.EncodeGoogleLabel(k)] = encode.EncodeGoogleLabel(v)
   713  	}
   714  
   715  	if len(labels) > 0 {
   716  		params["labels"] = labels
   717  	}
   718  
   719  	if tags, ok := secgroups[SECGROUP_TYPE_TAG]; ok && len(tags) > 0 {
   720  		for i := range tags {
   721  			tags[i] = strings.ToLower(tags[i])
   722  		}
   723  		params["tags"] = map[string][]string{
   724  			"items": tags,
   725  		}
   726  	}
   727  	if len(desc.UserData) > 0 {
   728  		params["metadata"] = map[string]interface{}{
   729  			"items": []struct {
   730  				Key   string
   731  				Value string
   732  			}{
   733  				{
   734  					Key:   METADATA_STARTUP_SCRIPT,
   735  					Value: desc.UserData,
   736  				},
   737  				{
   738  					Key:   METADATA_POWER_SHELL,
   739  					Value: desc.UserData,
   740  				},
   741  			},
   742  		}
   743  	}
   744  	if len(serviceAccounts) > 0 {
   745  		params["serviceAccounts"] = []struct {
   746  			Email  string
   747  			Scopes []string
   748  		}{
   749  			{
   750  				Email: serviceAccounts[0],
   751  				Scopes: []string{
   752  					"https://www.googleapis.com/auth/devstorage.read_only",
   753  					"https://www.googleapis.com/auth/logging.write",
   754  					"https://www.googleapis.com/auth/monitoring.write",
   755  					"https://www.googleapis.com/auth/servicecontrol",
   756  					"https://www.googleapis.com/auth/service.management.readonly",
   757  					"https://www.googleapis.com/auth/trace.append",
   758  				},
   759  			},
   760  		}
   761  	}
   762  	log.Debugf("create google instance params: %s", jsonutils.Marshal(params).String())
   763  	instance := &SInstance{}
   764  	resource := fmt.Sprintf("zones/%s/instances", zone)
   765  	err = region.Insert(resource, jsonutils.Marshal(params), instance)
   766  	if err != nil {
   767  		return nil, err
   768  	}
   769  	return instance, nil
   770  }
   771  
   772  func (region *SRegion) StartInstance(id string) error {
   773  	params := map[string]string{}
   774  	return region.Do(id, "start", nil, jsonutils.Marshal(params))
   775  }
   776  
   777  func (region *SRegion) StopInstance(id string) error {
   778  	params := map[string]string{}
   779  	return region.Do(id, "stop", nil, jsonutils.Marshal(params))
   780  }
   781  
   782  func (region *SRegion) ResetInstance(id string) error {
   783  	params := map[string]string{}
   784  	return region.Do(id, "reset", nil, jsonutils.Marshal(params))
   785  }
   786  
   787  func (region *SRegion) DetachDisk(instanceId, deviceName string) error {
   788  	body := map[string]string{}
   789  	params := map[string]string{"deviceName": deviceName}
   790  	return region.Do(instanceId, "detachDisk", params, jsonutils.Marshal(body))
   791  }
   792  
   793  func (instance *SInstance) GetSerialOutput(port int) (string, error) {
   794  	return instance.host.zone.region.GetSerialPortOutput(instance.SelfLink, port)
   795  }
   796  
   797  func (region *SRegion) GetSerialPortOutput(id string, port int) (string, error) {
   798  	_content, content, next := "", "", 0
   799  	var err error = nil
   800  	for {
   801  		_content, next, err = region.getSerialPortOutput(id, port, next)
   802  		if err != nil {
   803  			return content, err
   804  		}
   805  		content += _content
   806  		if len(_content) == 0 {
   807  			break
   808  		}
   809  	}
   810  	return content, nil
   811  }
   812  
   813  func (region *SRegion) getSerialPortOutput(id string, port int, start int) (string, int, error) {
   814  	resource := fmt.Sprintf("%s/serialPort?port=%d&start=%d", id, port, start)
   815  	result := struct {
   816  		Contents string
   817  		Start    int
   818  		Next     int
   819  	}{}
   820  	err := region.GetBySelfId(resource, &result)
   821  	if err != nil {
   822  		return "", result.Next, errors.Wrap(err, "")
   823  	}
   824  	return result.Contents, result.Next, nil
   825  }
   826  
   827  func (self *SRegion) AttachDisk(instanceId, diskId string, boot bool) error {
   828  	disk, err := self.GetDisk(diskId)
   829  	if err != nil {
   830  		return errors.Wrapf(err, "GetDisk(%s)", diskId)
   831  	}
   832  	body := map[string]interface{}{
   833  		"source": disk.SelfLink,
   834  		"boot":   boot,
   835  	}
   836  	if boot {
   837  		body["autoDelete"] = true
   838  	}
   839  	params := map[string]string{}
   840  	return self.Do(instanceId, "attachDisk", params, jsonutils.Marshal(body))
   841  }
   842  
   843  func (region *SRegion) ChangeInstanceConfig(id string, zone string, instanceType string, cpu int, memoryMb int) error {
   844  	if len(instanceType) == 0 {
   845  		instanceType = fmt.Sprintf("custom-%d-%d", cpu, memoryMb)
   846  	}
   847  	params := map[string]string{
   848  		"machineType": fmt.Sprintf("zones/%s/machineTypes/%s", zone, instanceType),
   849  	}
   850  	return region.Do(id, "setMachineType", nil, jsonutils.Marshal(params))
   851  }
   852  
   853  func (region *SRegion) SetMetadata(id string, metadata SMetadata) error {
   854  	return region.Do(id, "setMetadata", nil, jsonutils.Marshal(metadata))
   855  }
   856  
   857  func (region *SRegion) SetResourceTags(id string, tags SInstanceTag) error {
   858  	return region.Do(id, "setTags", nil, jsonutils.Marshal(tags))
   859  }
   860  
   861  func (region *SRegion) SetServiceAccount(id string, email string) error {
   862  	body := map[string]interface{}{
   863  		"email": email,
   864  		"scopes": []string{
   865  			"https://www.googleapis.com/auth/devstorage.read_only",
   866  			"https://www.googleapis.com/auth/logging.write",
   867  			"https://www.googleapis.com/auth/monitoring.write",
   868  			"https://www.googleapis.com/auth/servicecontrol",
   869  			"https://www.googleapis.com/auth/service.management.readonly",
   870  			"https://www.googleapis.com/auth/trace.append",
   871  		},
   872  	}
   873  	return region.Do(id, "setsetServiceAccount", nil, jsonutils.Marshal(body))
   874  }
   875  
   876  func (region *SRegion) RebuildRoot(instanceId string, imageId string, sysDiskSizeGb int) (string, error) {
   877  	oldDisk, diskType, deviceName := "", api.STORAGE_GOOGLE_PD_STANDARD, ""
   878  	instance, err := region.GetInstance(instanceId)
   879  	if err != nil {
   880  		return "", errors.Wrap(err, "region.GetInstance")
   881  	}
   882  	for _, disk := range instance.Disks {
   883  		if disk.Boot {
   884  			oldDisk = disk.Source
   885  			deviceName = disk.DeviceName
   886  			break
   887  		}
   888  	}
   889  
   890  	if len(oldDisk) > 0 {
   891  		disk := &SDisk{}
   892  		err := region.GetBySelfId(oldDisk, disk)
   893  		if err != nil {
   894  			return "", errors.Wrap(err, "region.GetDisk")
   895  		}
   896  		diskType = disk.Type
   897  		if sysDiskSizeGb == 0 {
   898  			sysDiskSizeGb = disk.SizeGB
   899  		}
   900  	}
   901  
   902  	zone, err := region.GetZone(instance.Zone)
   903  	if err != nil {
   904  		return "", errors.Wrap(err, "region.GetZone")
   905  	}
   906  
   907  	diskName := fmt.Sprintf("vdisk-%s-%d", instance.Name, time.Now().UnixNano())
   908  	disk, err := region.CreateDisk(diskName, sysDiskSizeGb, zone.Name, diskType, imageId, "create for replace instance system disk")
   909  	if err != nil {
   910  		return "", errors.Wrap(err, "region.CreateDisk.systemDisk")
   911  	}
   912  
   913  	if len(deviceName) > 0 {
   914  		err = region.DetachDisk(instance.SelfLink, deviceName)
   915  		if err != nil {
   916  			defer region.Delete(disk.SelfLink)
   917  			return "", errors.Wrap(err, "region.DetachDisk")
   918  		}
   919  	}
   920  
   921  	err = region.AttachDisk(instance.SelfLink, disk.Id, true)
   922  	if err != nil {
   923  		if len(oldDisk) > 0 {
   924  			defer region.AttachDisk(instance.SelfLink, oldDisk, true)
   925  		}
   926  		defer region.Delete(disk.SelfLink)
   927  		return "", errors.Wrap(err, "region.AttachDisk.newSystemDisk")
   928  	}
   929  
   930  	if len(oldDisk) > 0 {
   931  		defer region.Delete(oldDisk)
   932  	}
   933  	return disk.GetGlobalId(), nil
   934  }
   935  
   936  func (self *SRegion) SaveImage(diskId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
   937  	params := map[string]interface{}{
   938  		"name":        opts.Name,
   939  		"description": opts.Notes,
   940  		"sourceDisk":  diskId,
   941  	}
   942  	image := &SImage{}
   943  	err := self.Insert("global/images", jsonutils.Marshal(params), image)
   944  	if err != nil {
   945  		return nil, errors.Wrapf(err, "Insert")
   946  	}
   947  	image.storagecache = self.getStoragecache()
   948  	return image, nil
   949  }
   950  
   951  func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
   952  	for i := range self.Disks {
   953  		if self.Disks[0].Index == 0 {
   954  			image, err := self.host.zone.region.SaveImage(self.Disks[i].Source, opts)
   955  			if err != nil {
   956  				return nil, errors.Wrapf(err, "SaveImage")
   957  			}
   958  			return image, nil
   959  		}
   960  	}
   961  	return nil, errors.Wrapf(cloudprovider.ErrNotFound, "no valid system disk found")
   962  }
   963  
   964  func (region *SRegion) SetLabels(id string, _labels map[string]string, labelFingerprint string) error {
   965  	labels := map[string]string{}
   966  	for k, v := range _labels {
   967  		labels[encode.EncodeGoogleLabel(k)] = encode.EncodeGoogleLabel(v)
   968  	}
   969  	params := map[string]interface{}{
   970  		"labels":           labels,
   971  		"labelFingerprint": labelFingerprint,
   972  	}
   973  	err := region.Do(id, "setLabels", nil, jsonutils.Marshal(params))
   974  	if err != nil {
   975  		return errors.Wrapf(err, `region.Do(%s, "setLabels", nil, %s)`, id, jsonutils.Marshal(params).String())
   976  	}
   977  	return nil
   978  }
   979  
   980  func (self *SInstance) SetTags(tags map[string]string, replace bool) error {
   981  	if !replace {
   982  		oldTags, _ := self.GetTags()
   983  		for k, v := range oldTags {
   984  			if _, ok := tags[k]; !ok {
   985  				tags[k] = v
   986  			}
   987  		}
   988  	}
   989  	err := self.Refresh()
   990  	if err != nil {
   991  		return errors.Wrap(err, "self.Refresh()")
   992  	}
   993  	err = self.host.zone.region.SetLabels(self.SelfLink, tags, self.LabelFingerprint)
   994  	if err != nil {
   995  		return errors.Wrapf(err, ` self.host.zone.region.SsetLabels()`)
   996  	}
   997  	return nil
   998  }