yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/azure/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 azure
    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/util/osprofile"
    28  
    29  	"yunion.io/x/cloudmux/pkg/apis"
    30  	billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
    31  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    32  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    33  	"yunion.io/x/cloudmux/pkg/multicloud"
    34  	"yunion.io/x/onecloud/pkg/util/billing"
    35  	"yunion.io/x/onecloud/pkg/util/version"
    36  )
    37  
    38  const (
    39  	DEFAULT_EXTENSION_NAME = "enablevmaccess"
    40  )
    41  
    42  type HardwareProfile struct {
    43  	VMSize string `json:"vmSize,omitempty"`
    44  }
    45  
    46  type ImageReference struct {
    47  	Publisher string `json:"publisher,omitempty"`
    48  	Offer     string `json:"offer,omitempty"`
    49  	Sku       string `json:"sku,omitempty"`
    50  	Version   string `json:"version,omitempty"`
    51  	ID        string `json:"id,omitempty"`
    52  }
    53  
    54  type VirtualHardDisk struct {
    55  	Uri string `json:"uri,omitempty"`
    56  }
    57  
    58  type ManagedDiskParameters struct {
    59  	StorageAccountType string `json:"storageAccountType,omitempty"`
    60  	ID                 string
    61  }
    62  
    63  type StorageProfile struct {
    64  	ImageReference ImageReference `json:"imageReference,omitempty"`
    65  	OsDisk         SOsDisk        `json:"osDisk,omitempty"`
    66  	DataDisks      []SDataDisk    `json:"dataDisks,allowempty"`
    67  }
    68  
    69  type SSHPublicKey struct {
    70  	Path    string `json:"path,omitempty"`
    71  	KeyData string `json:"keyData,omitempty"`
    72  }
    73  
    74  type SSHConfiguration struct {
    75  	PublicKeys []SSHPublicKey `json:"publicKeys,omitempty"`
    76  }
    77  
    78  type LinuxConfiguration struct {
    79  	DisablePasswordAuthentication bool              `json:"disablePasswordAuthentication,omitempty"`
    80  	SSH                           *SSHConfiguration `json:"ssh,omitempty"`
    81  }
    82  
    83  type VaultCertificate struct {
    84  	CertificateURL   string `json:"certificateURL,omitempty"`
    85  	CertificateStore string `json:"certificateStore,omitempty"`
    86  }
    87  
    88  type VaultSecretGroup struct {
    89  	SourceVault       SubResource        `json:"sourceVault,omitempty"`
    90  	VaultCertificates []VaultCertificate `json:"vaultCertificates,omitempty"`
    91  }
    92  
    93  type OsProfile struct {
    94  	ComputerName       string              `json:"computerName,omitempty"`
    95  	AdminUsername      string              `json:"adminUsername,omitempty"`
    96  	AdminPassword      string              `json:"adminPassword,omitempty"`
    97  	CustomData         string              `json:"customData,omitempty"`
    98  	LinuxConfiguration *LinuxConfiguration `json:"linuxConfiguration,omitempty"`
    99  	Secrets            []VaultSecretGroup  `json:"secrets,omitempty"`
   100  }
   101  
   102  type NetworkInterfaceReference struct {
   103  	ID string
   104  }
   105  
   106  type NetworkProfile struct {
   107  	NetworkInterfaces []NetworkInterfaceReference `json:"networkInterfaces,omitempty"`
   108  }
   109  
   110  type Statuses struct {
   111  	Code          string
   112  	Level         string
   113  	DisplayStatus string `json:"displayStatus,omitempty"`
   114  	Message       string
   115  	//Time          time.Time
   116  }
   117  
   118  type SVMAgent struct {
   119  	VmAgentVersion string     `json:"vmAgentVersion,omitempty"`
   120  	Statuses       []Statuses `json:"statuses,omitempty"`
   121  }
   122  
   123  type SExtension struct {
   124  	Name               string
   125  	Type               string
   126  	TypeHandlerVersion string     `json:"typeHandlerVersion,omitempty"`
   127  	Statuses           []Statuses `json:"statuses,omitempty"`
   128  }
   129  
   130  type VirtualMachineInstanceView struct {
   131  	Statuses   []Statuses   `json:"statuses,omitempty"`
   132  	VMAgent    SVMAgent     `json:"vmAgent,omitempty"`
   133  	Extensions []SExtension `json:"extensions,omitempty"`
   134  }
   135  
   136  type DomainName struct {
   137  	Id   string
   138  	Name string
   139  	Type string
   140  }
   141  
   142  type DebugProfile struct {
   143  	BootDiagnosticsEnabled   *bool  `json:"bootDiagnosticsEnabled,omitempty"`
   144  	ConsoleScreenshotBlobUri string `json:"consoleScreenshotBlobUri,omitempty"`
   145  	SerialOutputBlobUri      string `json:"serialOutputBlobUri,omitempty"`
   146  }
   147  
   148  type VirtualMachineProperties struct {
   149  	ProvisioningState string                      `json:"provisioningState,omitempty"`
   150  	InstanceView      *VirtualMachineInstanceView `json:"instanceView,omitempty"`
   151  	DomainName        *DomainName                 `json:"domainName,omitempty"`
   152  	HardwareProfile   HardwareProfile             `json:"hardwareProfile,omitempty"`
   153  	NetworkProfile    NetworkProfile              `json:"networkProfile,omitempty"`
   154  	StorageProfile    StorageProfile              `json:"storageProfile,omitempty"`
   155  	DebugProfile      *DebugProfile               `json:"debugProfile,omitempty"`
   156  	OsProfile         OsProfile                   `json:"osProfile,omitempty"`
   157  	VmId              string                      `json:"vmId,omitempty"`
   158  	TimeCreated       time.Time                   `json:"timeCreated,omitempty"`
   159  }
   160  
   161  type SExtensionResourceProperties struct {
   162  	AutoUpgradeMinorVersion bool
   163  	ProvisioningState       string
   164  	Publisher               string
   165  	Type                    string
   166  	TypeHandlerVersion      string
   167  }
   168  
   169  type SExtensionResource struct {
   170  	Id       string
   171  	Name     string
   172  	Type     string
   173  	Location string
   174  
   175  	Properties SExtensionResourceProperties
   176  }
   177  
   178  type SInstance struct {
   179  	multicloud.SInstanceBase
   180  	AzureTags
   181  	host *SHost
   182  
   183  	Properties VirtualMachineProperties
   184  	ID         string
   185  	Name       string
   186  	Type       string
   187  	Location   string
   188  	vmSize     *SVMSize
   189  
   190  	Resources []SExtensionResource
   191  }
   192  
   193  func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) {
   194  	instance := SInstance{}
   195  	params := url.Values{}
   196  	params.Set("$expand", "instanceView")
   197  	return &instance, self.get(instanceId, params, &instance)
   198  }
   199  
   200  func (self *SRegion) GetInstanceScaleSets() ([]SInstance, error) {
   201  	instance := []SInstance{}
   202  	return instance, self.client.list("Microsoft.Compute/virtualMachineScaleSets", url.Values{}, &instance)
   203  }
   204  
   205  func (self *SRegion) GetInstances() ([]SInstance, error) {
   206  	result := []SInstance{}
   207  	resource := fmt.Sprintf("Microsoft.Compute/locations/%s/virtualMachines", self.Name)
   208  	err := self.client.list(resource, url.Values{}, &result)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  	return result, nil
   213  }
   214  
   215  func (self *SRegion) doDeleteVM(instanceId string) error {
   216  	return self.del(instanceId)
   217  }
   218  
   219  func (self *SInstance) GetSecurityGroupIds() ([]string, error) {
   220  	secgroupIds := []string{}
   221  	if nics, err := self.getNics(); err == nil {
   222  		for _, nic := range nics {
   223  			if len(nic.Properties.NetworkSecurityGroup.ID) > 0 {
   224  				secgroupIds = append(secgroupIds, strings.ToLower(nic.Properties.NetworkSecurityGroup.ID))
   225  			}
   226  		}
   227  	}
   228  	return secgroupIds, nil
   229  }
   230  
   231  func (self *SInstance) GetSysTags() map[string]string {
   232  	data := map[string]string{}
   233  	if osDistribution := self.Properties.StorageProfile.ImageReference.Publisher; len(osDistribution) > 0 {
   234  		data["os_distribution"] = osDistribution
   235  	}
   236  	if loginAccount := self.Properties.OsProfile.AdminUsername; len(loginAccount) > 0 {
   237  		data["login_account"] = loginAccount
   238  	}
   239  	if loginKey := self.Properties.OsProfile.AdminPassword; len(loginKey) > 0 {
   240  		data["login_key"] = loginKey
   241  	}
   242  	for _, res := range self.Resources {
   243  		if strings.HasSuffix(strings.ToLower(res.Id), "databricksbootstrap") {
   244  			data[apis.IS_SYSTEM] = "true"
   245  			break
   246  		}
   247  	}
   248  	return data
   249  }
   250  
   251  func (self *SInstance) GetTags() (map[string]string, error) {
   252  	return self.Tags, nil
   253  }
   254  
   255  func (self *SInstance) GetHypervisor() string {
   256  	return api.HYPERVISOR_AZURE
   257  }
   258  
   259  func (self *SInstance) GetInstanceType() string {
   260  	return self.Properties.HardwareProfile.VMSize
   261  }
   262  
   263  func (self *SInstance) WaitVMAgentReady() error {
   264  	status := ""
   265  	err := cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) {
   266  		if self.Properties.InstanceView == nil {
   267  			self.Refresh()
   268  			return false, nil
   269  		}
   270  
   271  		for _, vmAgent := range self.Properties.InstanceView.VMAgent.Statuses {
   272  			status = vmAgent.DisplayStatus
   273  			if status == "Ready" {
   274  				break
   275  			}
   276  			log.Debugf("vmAgent %s status: %s waite for ready", self.Properties.InstanceView.VMAgent.VmAgentVersion, vmAgent.DisplayStatus)
   277  		}
   278  		if status == "Ready" {
   279  			return true, nil
   280  		}
   281  		return false, self.Refresh()
   282  	})
   283  	if err != nil {
   284  		return errors.Wrapf(err, "waitting vmAgent ready, current status: %s", status)
   285  	}
   286  	return nil
   287  
   288  }
   289  
   290  func (self *SInstance) WaitEnableVMAccessReady() error {
   291  	if self.Properties.InstanceView == nil {
   292  		return fmt.Errorf("instance may not install VMAgent or VMAgent not running")
   293  	}
   294  	if len(self.Properties.InstanceView.VMAgent.VmAgentVersion) > 0 {
   295  		return self.WaitVMAgentReady()
   296  	}
   297  
   298  	for _, extension := range self.Properties.InstanceView.Extensions {
   299  		if extension.Name == "enablevmaccess" {
   300  			displayStatus := ""
   301  			for _, status := range extension.Statuses {
   302  				displayStatus = status.DisplayStatus
   303  				if displayStatus == "Provisioning succeeded" {
   304  					return nil
   305  				}
   306  			}
   307  			return self.host.zone.region.deleteExtension(self.ID, "enablevmaccess")
   308  		}
   309  	}
   310  
   311  	return fmt.Errorf("instance may not install VMAgent or VMAgent not running")
   312  }
   313  
   314  func (self *SInstance) getNics() ([]SInstanceNic, error) {
   315  	nics := []SInstanceNic{}
   316  	for _, _nic := range self.Properties.NetworkProfile.NetworkInterfaces {
   317  		nic, err := self.host.zone.region.GetNetworkInterface(_nic.ID)
   318  		if err != nil {
   319  			return nil, errors.Wrapf(err, "GetNetworkInterface(%s)", _nic.ID)
   320  		}
   321  		nic.instance = self
   322  		nics = append(nics, *nic)
   323  	}
   324  	return nics, nil
   325  }
   326  
   327  func (self *SInstance) Refresh() error {
   328  	instance, err := self.host.zone.region.GetInstance(self.ID)
   329  	if err != nil {
   330  		return err
   331  	}
   332  	err = jsonutils.Update(self, instance)
   333  	if err != nil {
   334  		return err
   335  	}
   336  	self.Tags = instance.Tags
   337  	return nil
   338  }
   339  
   340  func (self *SInstance) GetStatus() string {
   341  	if self.Properties.InstanceView == nil {
   342  		err := self.Refresh()
   343  		if err != nil {
   344  			log.Errorf("failed to get status for instance %s", self.Name)
   345  			return api.VM_UNKNOWN
   346  		}
   347  	}
   348  	for _, statuses := range self.Properties.InstanceView.Statuses {
   349  		if code := strings.Split(statuses.Code, "/"); len(code) == 2 {
   350  			if code[0] == "PowerState" {
   351  				switch code[1] {
   352  				case "stopped", "deallocated":
   353  					return api.VM_READY
   354  				case "running":
   355  					return api.VM_RUNNING
   356  				case "stopping":
   357  					return api.VM_STOPPING
   358  				case "starting":
   359  					return api.VM_STARTING
   360  				case "deleting":
   361  					return api.VM_DELETING
   362  				default:
   363  					log.Errorf("Unknow instance status %s", code[1])
   364  					return api.VM_UNKNOWN
   365  				}
   366  			}
   367  		}
   368  		if statuses.Level == "Error" {
   369  			log.Errorf("Find error code: [%s] message: %s", statuses.Code, statuses.Message)
   370  		}
   371  	}
   372  	return api.VM_UNKNOWN
   373  }
   374  
   375  func (self *SInstance) GetIHost() cloudprovider.ICloudHost {
   376  	return self.host
   377  }
   378  
   379  func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
   380  	return self.host.zone.region.AttachDisk(self.ID, diskId)
   381  }
   382  
   383  func (region *SRegion) AttachDisk(instanceId, diskId string) error {
   384  	instance, err := region.GetInstance(instanceId)
   385  	if err != nil {
   386  		return errors.Wrapf(err, "GetInstance(%s)", instanceId)
   387  	}
   388  	lunMaps := map[int32]bool{}
   389  	dataDisks := jsonutils.NewArray()
   390  	for _, disk := range instance.Properties.StorageProfile.DataDisks {
   391  		if disk.ManagedDisk != nil && strings.ToLower(disk.ManagedDisk.ID) == strings.ToLower(diskId) {
   392  			return nil
   393  		}
   394  		lunMaps[disk.Lun] = true
   395  		dataDisks.Add(jsonutils.Marshal(disk))
   396  	}
   397  	lun := func() int32 {
   398  		idx := int32(0)
   399  		for {
   400  			if _, ok := lunMaps[idx]; !ok {
   401  				return idx
   402  			}
   403  			idx++
   404  		}
   405  	}()
   406  	dataDisks.Add(jsonutils.Marshal(map[string]interface{}{
   407  		"Lun":          lun,
   408  		"CreateOption": "Attach",
   409  		"ManagedDisk": map[string]string{
   410  			"Id": diskId,
   411  		},
   412  	}))
   413  	params := jsonutils.NewDict()
   414  	params.Add(dataDisks, "Properties", "StorageProfile", "DataDisks")
   415  	params.Add(jsonutils.Marshal(instance.Properties.StorageProfile.OsDisk), "Properties", "StorageProfile", "OsDisk")
   416  	_, err = region.patch(instanceId, params)
   417  	return err
   418  }
   419  
   420  func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
   421  	return self.host.zone.region.DetachDisk(self.ID, diskId)
   422  }
   423  
   424  func (region *SRegion) DetachDisk(instanceId, diskId string) error {
   425  	instance, err := region.GetInstance(instanceId)
   426  	if err != nil {
   427  		return errors.Wrapf(err, "GetInstance(%s)", instanceId)
   428  	}
   429  	diskMaps := map[string]bool{}
   430  	dataDisks := jsonutils.NewArray()
   431  	for _, disk := range instance.Properties.StorageProfile.DataDisks {
   432  		if disk.ManagedDisk != nil {
   433  			diskMaps[strings.ToLower(disk.ManagedDisk.ID)] = true
   434  			if strings.ToLower(disk.ManagedDisk.ID) == strings.ToLower(diskId) {
   435  				continue
   436  			}
   437  		}
   438  		dataDisks.Add(jsonutils.Marshal(disk))
   439  	}
   440  	if _, ok := diskMaps[strings.ToLower(diskId)]; !ok {
   441  		log.Warningf("not find disk %s with instance %s", diskId, instance.Name)
   442  		return nil
   443  	}
   444  	params := jsonutils.NewDict()
   445  	params.Add(dataDisks, "Properties", "StorageProfile", "DataDisks")
   446  	params.Add(jsonutils.Marshal(instance.Properties.StorageProfile.OsDisk), "Properties", "StorageProfile", "OsDisk")
   447  	_, err = region.patch(instanceId, params)
   448  	return err
   449  }
   450  
   451  func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
   452  	if len(config.InstanceType) > 0 {
   453  		return self.host.zone.region.ChangeConfig(self.ID, config.InstanceType)
   454  	}
   455  	var err error
   456  	for _, vmSize := range self.host.zone.region.getHardwareProfile(config.Cpu, config.MemoryMB) {
   457  		log.Debugf("Try HardwareProfile : %s", vmSize)
   458  		err = self.host.zone.region.ChangeConfig(self.ID, vmSize)
   459  		if err == nil {
   460  			return nil
   461  		}
   462  	}
   463  	if err != nil {
   464  		return errors.Wrap(err, "ChangeConfig")
   465  	}
   466  	return fmt.Errorf("Failed to change vm config, specification not supported")
   467  }
   468  
   469  func (self *SRegion) ChangeConfig(instanceId, instanceType string) error {
   470  	params := map[string]interface{}{
   471  		"Properties": map[string]interface{}{
   472  			"HardwareProfile": map[string]string{
   473  				"vmSize": instanceType,
   474  			},
   475  		},
   476  	}
   477  	log.Debugf("Try HardwareProfile : %s", instanceType)
   478  	_, err := self.patch(instanceId, jsonutils.Marshal(params))
   479  	return err
   480  }
   481  
   482  func (region *SRegion) ChangeVMConfig(ctx context.Context, instanceId string, ncpu int, vmem int) error {
   483  	instacen, err := region.GetInstance(instanceId)
   484  	if err != nil {
   485  		return err
   486  	}
   487  	return instacen.ChangeConfig(ctx, &cloudprovider.SManagedVMChangeConfig{Cpu: ncpu, MemoryMB: vmem})
   488  }
   489  
   490  func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error {
   491  	if len(publicKey) > 0 || len(password) > 0 {
   492  		// 先判断系统是否安装了vmAgent,然后等待扩展准备完成后再重置密码
   493  		err := self.WaitEnableVMAccessReady()
   494  		if err != nil {
   495  			return err
   496  		}
   497  	}
   498  	return self.host.zone.region.DeployVM(ctx, self.ID, string(self.GetOsType()), name, password, publicKey, deleteKeypair, description)
   499  }
   500  
   501  type VirtualMachineExtensionProperties struct {
   502  	Publisher          string      `json:"publisher,omitempty"`
   503  	Type               string      `json:"type,omitempty"`
   504  	TypeHandlerVersion string      `json:"typeHandlerVersion,omitempty"`
   505  	ProtectedSettings  interface{} `json:"protectedSettings,omitempty"`
   506  	Settings           interface{} `json:"settings,omitempty"`
   507  }
   508  
   509  type SVirtualMachineExtension struct {
   510  	Location   string                            `json:"location,omitempty"`
   511  	Properties VirtualMachineExtensionProperties `json:"properties,omitempty"`
   512  }
   513  
   514  func (region *SRegion) execOnLinux(instanceId string, command string) error {
   515  	extension := SVirtualMachineExtension{
   516  		Location: region.Name,
   517  		Properties: VirtualMachineExtensionProperties{
   518  			Publisher:          "Microsoft.Azure.Extensions",
   519  			Type:               "CustomScript",
   520  			TypeHandlerVersion: "2.0",
   521  			Settings:           map[string]string{"commandToExecute": command},
   522  		},
   523  	}
   524  	resource := fmt.Sprintf("%s/extensions/CustomScript", instanceId)
   525  	_, err := region.put(resource, jsonutils.Marshal(extension))
   526  	return err
   527  }
   528  
   529  func (region *SRegion) resetOvsEnv(instanceId string) error {
   530  	ovsEnv, err := region.getOvsEnv(instanceId)
   531  	if err != nil {
   532  		return err
   533  	}
   534  	err = region.execOnLinux(instanceId, fmt.Sprintf(`echo '%s' > /var/lib/waagent/ovf-env.xml`, ovsEnv))
   535  	if err != nil {
   536  		return err
   537  	}
   538  	return region.execOnLinux(instanceId, "systemctl restart wagent")
   539  }
   540  
   541  func (region *SRegion) deleteExtension(instanceId, extensionName string) error {
   542  	return region.del(fmt.Sprintf("%s/extensions/%s", instanceId, extensionName))
   543  }
   544  func (region *SRegion) resetLoginInfo(osType, instanceId string, setting map[string]interface{}) error {
   545  	// https://github.com/Azure/azure-linux-extensions/blob/master/VMAccess/README.md
   546  	handlerVersion := "1.5"
   547  	properties := map[string]interface{}{
   548  		"Publisher":          "Microsoft.OSTCExtensions",
   549  		"Type":               "VMAccessForLinux",
   550  		"TypeHandlerVersion": handlerVersion,
   551  		"Settings":           map[string]string{},
   552  		"protectedSettings":  setting,
   553  
   554  		"autoUpgradeMinorVersion": true,
   555  	}
   556  	if osType == osprofile.OS_TYPE_WINDOWS {
   557  		// https://github.com/Azure/azure-cli/blob/dev/src/azure-cli/azure/cli/command_modules/vm/custom.py
   558  		handlerVersion = "2.4"
   559  		properties["TypeHandlerVersion"] = handlerVersion
   560  		properties["Publisher"] = "Microsoft.Compute"
   561  		properties["Type"] = "VMAccessAgent"
   562  	}
   563  	params := map[string]interface{}{
   564  		"Location":   region.Name,
   565  		"Properties": properties,
   566  	}
   567  	instance, err := region.GetInstance(instanceId)
   568  	if err != nil {
   569  		return errors.Wrapf(err, "GetInstance(%s)", instanceId)
   570  	}
   571  	for _, extension := range instance.Resources {
   572  		if extension.Name == "enablevmaccess" {
   573  			if version.GT(extension.Properties.TypeHandlerVersion, handlerVersion) {
   574  				properties["TypeHandlerVersion"] = extension.Properties.TypeHandlerVersion
   575  				break
   576  			}
   577  		}
   578  	}
   579  	resource := fmt.Sprintf("%s/extensions/enablevmaccess", instanceId)
   580  	_, err = region.put(resource, jsonutils.Marshal(params))
   581  	if err != nil {
   582  		switch osType {
   583  		case osprofile.OS_TYPE_WINDOWS:
   584  			return err
   585  		default:
   586  			err = region.deleteExtension(instanceId, "enablevmaccess")
   587  			if err != nil {
   588  				return err
   589  			}
   590  			err = region.resetOvsEnv(instanceId)
   591  			if err != nil {
   592  				return err
   593  			}
   594  			resource := fmt.Sprintf("%s/extensions/enablevmaccess", instanceId)
   595  			_, err = region.put(resource, jsonutils.Marshal(params))
   596  			return err
   597  		}
   598  	}
   599  	err = cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) {
   600  		instance, err := region.GetInstance(instanceId)
   601  		if err != nil {
   602  			return false, errors.Wrapf(err, "GetInstance(%s)", instanceId)
   603  		}
   604  		for _, extension := range instance.Resources {
   605  			if extension.Name == "enablevmaccess" {
   606  				if extension.Properties.ProvisioningState == "Succeeded" {
   607  					return true, nil
   608  				}
   609  				log.Debugf("enablevmaccess status %s expect Succeeded", extension.Properties.ProvisioningState)
   610  				if extension.Properties.ProvisioningState == "Failed" {
   611  					if instance.Properties.InstanceView != nil {
   612  						for _, info := range instance.Properties.InstanceView.Extensions {
   613  							if info.Name == "enablevmaccess" && len(info.Statuses) > 0 {
   614  								return false, fmt.Errorf("details: %s", jsonutils.Marshal(info.Statuses))
   615  							}
   616  						}
   617  					}
   618  					return false, fmt.Errorf("reset passwod failed")
   619  				}
   620  			}
   621  		}
   622  		return false, nil
   623  	})
   624  	if err != nil {
   625  		return errors.Wrapf(err, "wait for enablevmaccess error: %v", err)
   626  	}
   627  	return nil
   628  }
   629  
   630  func (region *SRegion) resetPublicKey(osType, instanceId string, username, publicKey string) error {
   631  	setting := map[string]interface{}{
   632  		"username": username,
   633  		"ssh_key":  publicKey,
   634  	}
   635  	return region.resetLoginInfo(osType, instanceId, setting)
   636  }
   637  
   638  func (region *SRegion) resetPassword(osType, instanceId, username, password string) error {
   639  	setting := map[string]interface{}{
   640  		"username": username,
   641  		"password": password,
   642  	}
   643  	return region.resetLoginInfo(osType, instanceId, setting)
   644  }
   645  
   646  func (region *SRegion) DeployVM(ctx context.Context, instanceId, osType, name, password, publicKey string, deleteKeypair bool, description string) error {
   647  	instance, err := region.GetInstance(instanceId)
   648  	if err != nil {
   649  		return err
   650  	}
   651  	if deleteKeypair {
   652  		return nil
   653  	}
   654  	if len(publicKey) > 0 {
   655  		return region.resetPublicKey(osType, instanceId, instance.Properties.OsProfile.AdminUsername, publicKey)
   656  	}
   657  	if len(password) > 0 {
   658  		return region.resetPassword(osType, instanceId, instance.Properties.OsProfile.AdminUsername, password)
   659  	}
   660  	return nil
   661  }
   662  
   663  func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
   664  	cpu := self.GetVcpuCount()
   665  	memoryMb := self.GetVmemSizeMB()
   666  	opts := &cloudprovider.ServerStopOptions{
   667  		IsForce: true,
   668  	}
   669  	self.StopVM(ctx, opts)
   670  	return self.host.zone.region.ReplaceSystemDisk(self, cpu, memoryMb, desc.ImageId, desc.Password, desc.PublicKey, desc.SysSizeGB)
   671  }
   672  
   673  func (region *SRegion) ReplaceSystemDisk(instance *SInstance, cpu int, memoryMb int, imageId, passwd, publicKey string, sysSizeGB int) (string, error) {
   674  	log.Debugf("ReplaceSystemDisk %s image: %s", instance.ID, imageId)
   675  	storageType := instance.Properties.StorageProfile.OsDisk.ManagedDisk.StorageAccountType
   676  	if len(storageType) == 0 {
   677  		_disk, err := region.GetDisk(instance.Properties.StorageProfile.OsDisk.ManagedDisk.ID)
   678  		if err != nil {
   679  			return "", err
   680  		}
   681  		storageType = _disk.Sku.Name
   682  	}
   683  	image, err := region.GetImageById(imageId)
   684  	if err != nil {
   685  		return "", errors.Wrapf(err, "GetImageById(%s)", imageId)
   686  	}
   687  	if minOsDiskSizeGB := image.GetMinOsDiskSizeGb(); minOsDiskSizeGB > sysSizeGB {
   688  		sysSizeGB = minOsDiskSizeGB
   689  	}
   690  	osType := instance.Properties.StorageProfile.OsDisk.OsType
   691  	// https://support.microsoft.com/zh-cn/help/4018933/the-default-size-of-windows-server-images-in-azure-is-changed-from-128
   692  	// windows默认系统盘是128G, 若重装系统时,系统盘小于128G,则会出现 {"error":{"code":"ResizeDiskError","message":"Disk size reduction is not supported. Current size is 137438953472 bytes, requested size is 33285996544 bytes.","target":"osDisk.diskSizeGB"}} 错误
   693  	if osType == osprofile.OS_TYPE_WINDOWS && sysSizeGB < 128 {
   694  		sysSizeGB = 128
   695  	}
   696  	nicId := instance.Properties.NetworkProfile.NetworkInterfaces[0].ID
   697  	nic, err := region.GetNetworkInterface(nicId)
   698  	if err != nil {
   699  		return "", errors.Wrapf(err, "GetNetworkInterface(%s)", nicId)
   700  	}
   701  	if len(nic.Properties.IPConfigurations) == 0 {
   702  		return "", fmt.Errorf("failed to find networkId for nic %s", nicId)
   703  	}
   704  	networkId := nic.Properties.IPConfigurations[0].Properties.Subnet.ID
   705  
   706  	nic, err = region.CreateNetworkInterface("", fmt.Sprintf("%s-temp-ifconfig", instance.Name), "", networkId, "")
   707  	if err != nil {
   708  		return "", errors.Wrapf(err, "CreateNetworkInterface")
   709  	}
   710  
   711  	newInstance, err := region.CreateInstanceSimple(instance.Name+"-rebuild", imageId, osType, cpu, memoryMb, sysSizeGB, storageType, []int{}, nic.ID, passwd, publicKey)
   712  	newInstance.host = instance.host
   713  	cloudprovider.Wait(time.Second*10, time.Minute*5, func() (bool, error) {
   714  		err = newInstance.WaitVMAgentReady()
   715  		if err != nil {
   716  			log.Warningf("WaitVMAgentReady for %s error: %v", newInstance.Name, err)
   717  			return false, nil
   718  		}
   719  		return true, nil
   720  	})
   721  
   722  	opts := &cloudprovider.ServerStopOptions{
   723  		IsForce: true,
   724  	}
   725  	newInstance.StopVM(context.Background(), opts)
   726  
   727  	pruneDiskId := instance.Properties.StorageProfile.OsDisk.ManagedDisk.ID
   728  	newDiskId := newInstance.Properties.StorageProfile.OsDisk.ManagedDisk.ID
   729  
   730  	err = newInstance.deleteVM(context.TODO(), true)
   731  	if err != nil {
   732  		log.Warningf("delete vm %s error: %v", newInstance.ID, err)
   733  	}
   734  
   735  	defer func() {
   736  		if len(pruneDiskId) > 0 {
   737  			err := cloudprovider.Wait(time.Second*3, time.Minute, func() (bool, error) {
   738  				err = region.DeleteDisk(pruneDiskId)
   739  				if err != nil {
   740  					log.Errorf("delete prune disk %s error: %v", pruneDiskId, err)
   741  					return false, nil
   742  				}
   743  				return true, nil
   744  			})
   745  			if err != nil {
   746  				log.Errorf("timeout for delete prune disk %s", pruneDiskId)
   747  			}
   748  		}
   749  	}()
   750  
   751  	//交换系统盘
   752  	params := map[string]interface{}{
   753  		"Id":       instance.ID,
   754  		"Location": instance.Location,
   755  		"Properties": map[string]interface{}{
   756  			"StorageProfile": map[string]interface{}{
   757  				"OsDisk": map[string]interface{}{
   758  					"createOption": "FromImage",
   759  					"ManagedDisk": map[string]interface{}{
   760  						"Id":                 newDiskId,
   761  						"storageAccountType": nil,
   762  					},
   763  					"osType":     osType,
   764  					"DiskSizeGB": nil,
   765  				},
   766  			},
   767  		},
   768  	}
   769  	err = region.update(jsonutils.Marshal(params), nil)
   770  	if err != nil {
   771  		// 更新失败,需要删除新建的系统盘
   772  		pruneDiskId = newDiskId
   773  		return "", errors.Wrapf(err, "region.update")
   774  	}
   775  	return strings.ToLower(newDiskId), nil
   776  }
   777  
   778  func (self *SInstance) UpdateVM(ctx context.Context, name string) error {
   779  	return cloudprovider.ErrNotSupported
   780  }
   781  
   782  func (self *SInstance) GetId() string {
   783  	return self.ID
   784  }
   785  
   786  func (self *SInstance) GetName() string {
   787  	return self.Name
   788  }
   789  
   790  func (self *SInstance) GetHostname() string {
   791  	return self.Name
   792  }
   793  
   794  func (self *SInstance) GetGlobalId() string {
   795  	return strings.ToLower(self.ID)
   796  }
   797  
   798  func (self *SRegion) DeleteVM(instanceId string) error {
   799  	return self.doDeleteVM(instanceId)
   800  }
   801  
   802  func (self *SInstance) deleteVM(ctx context.Context, keepSysDisk bool) error {
   803  	sysDiskId := ""
   804  	if self.Properties.StorageProfile.OsDisk.ManagedDisk != nil {
   805  		sysDiskId = self.Properties.StorageProfile.OsDisk.ManagedDisk.ID
   806  	}
   807  	err := self.host.zone.region.DeleteVM(self.ID)
   808  	if err != nil {
   809  		return err
   810  	}
   811  	if len(sysDiskId) > 0 && !keepSysDisk {
   812  		err := self.host.zone.region.DeleteDisk(sysDiskId)
   813  		if err != nil {
   814  			return err
   815  		}
   816  	}
   817  
   818  	nics, err := self.getNics()
   819  	if err != nil {
   820  		return err
   821  	}
   822  	for _, nic := range nics {
   823  		if err := nic.Delete(); err != nil {
   824  			if err != cloudprovider.ErrNotFound {
   825  				return err
   826  			}
   827  		}
   828  	}
   829  	return nil
   830  }
   831  
   832  func (self *SInstance) DeleteVM(ctx context.Context) error {
   833  	return self.deleteVM(ctx, false)
   834  }
   835  
   836  func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
   837  	disks := []cloudprovider.ICloudDisk{}
   838  	self.Properties.StorageProfile.OsDisk.region = self.host.zone.region
   839  	disks = append(disks, &self.Properties.StorageProfile.OsDisk)
   840  	for i := range self.Properties.StorageProfile.DataDisks {
   841  		self.Properties.StorageProfile.DataDisks[i].region = self.host.zone.region
   842  		disks = append(disks, &self.Properties.StorageProfile.DataDisks[i])
   843  	}
   844  	return disks, nil
   845  }
   846  
   847  func (self *SInstance) GetOsType() cloudprovider.TOsType {
   848  	return cloudprovider.TOsType(osprofile.NormalizeOSType(string(self.Properties.StorageProfile.OsDisk.OsType)))
   849  }
   850  
   851  func (self *SInstance) GetFullOsName() string {
   852  	return self.Properties.StorageProfile.ImageReference.Offer
   853  }
   854  
   855  func (self *SInstance) GetBios() cloudprovider.TBiosType {
   856  	return "BIOS"
   857  }
   858  
   859  func (i *SInstance) GetOsDist() string {
   860  	return ""
   861  }
   862  
   863  func (i *SInstance) GetOsVersion() string {
   864  	return ""
   865  }
   866  
   867  func (i *SInstance) GetOsLang() string {
   868  	return ""
   869  }
   870  
   871  func (i *SInstance) GetOsArch() string {
   872  	return apis.OS_ARCH_X86_64
   873  }
   874  
   875  func (self *SRegion) getOvsEnv(instanceId string) (string, error) {
   876  	instance, err := self.GetInstance(instanceId)
   877  	if err != nil {
   878  		return "", err
   879  	}
   880  	kms := map[string]string{
   881  		"AzureGermanCloud":       "kms.core.cloudapi.de",
   882  		"AzureChinaCloud":        "kms.core.chinacloudapi.cn",
   883  		"AzureUSGovernmentCloud": "kms.core.usgovcloudapi.net",
   884  		"AzurePublicCloud":       "kms.core.windows.net",
   885  	}
   886  	kmsServer := "kms.core.chinacloudapi.cn"
   887  	if _kmsServer, ok := kms[self.client.envName]; ok {
   888  		kmsServer = _kmsServer
   889  	}
   890  	return fmt.Sprintf(`
   891  	<ns0:Environment xmlns:ns0="http://schemas.dmtf.org/ovf/environment/1" xmlns:ns1="http://schemas.microsoft.com/windowsazure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   892  		<ns1:ProvisioningSection>
   893  		<ns1:Version>1.0</ns1:Version>
   894  		<ns1:LinuxProvisioningConfigurationSet>
   895  			<ns1:ConfigurationSetType>LinuxProvisioningConfiguration</ns1:ConfigurationSetType>
   896  			<ns1:UserName>%s</ns1:UserName>
   897  			<ns1:DisableSshPasswordAuthentication>false</ns1:DisableSshPasswordAuthentication>
   898  			<ns1:HostName>%s</ns1:HostName>
   899  			<ns1:UserPassword>REDACTED</ns1:UserPassword>
   900  		</ns1:LinuxProvisioningConfigurationSet>
   901  		</ns1:ProvisioningSection>
   902  			<ns1:PlatformSettingsSection>
   903  				<ns1:Version>1.0</ns1:Version>
   904  			<ns1:PlatformSettings>
   905  				<ns1:KmsServerHostname>%s</ns1:KmsServerHostname>
   906  				<ns1:ProvisionGuestAgent>true</ns1:ProvisionGuestAgent>
   907  				<ns1:GuestAgentPackageName xsi:nil="true" />
   908  				<ns1:RetainWindowsPEPassInUnattend>true</ns1:RetainWindowsPEPassInUnattend>
   909  				<ns1:RetainOfflineServicingPassInUnattend>true</ns1:RetainOfflineServicingPassInUnattend>
   910  				<ns1:PreprovisionedVm>false</ns1:PreprovisionedVm>
   911  				<ns1:EnableTrustedImageIdentifier>false</ns1:EnableTrustedImageIdentifier>
   912  			</ns1:PlatformSettings>
   913  		</ns1:PlatformSettingsSection>
   914  	</ns0:Environment>`, instance.Properties.OsProfile.AdminUsername, instance.Properties.OsProfile.ComputerName, kmsServer), nil
   915  }
   916  
   917  func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
   918  	nics := make([]cloudprovider.ICloudNic, 0)
   919  	_nics, err := self.getNics()
   920  	if err != nil {
   921  		return nil, err
   922  	}
   923  	for i := 0; i < len(_nics); i++ {
   924  		_nics[i].instance = self
   925  		nics = append(nics, &_nics[i])
   926  	}
   927  	return nics, nil
   928  }
   929  
   930  func (self *SInstance) GetMachine() string {
   931  	return "pc"
   932  }
   933  
   934  func (self *SInstance) GetBootOrder() string {
   935  	return "dcn"
   936  }
   937  
   938  func (self *SInstance) GetVga() string {
   939  	return "std"
   940  }
   941  
   942  func (self *SInstance) GetVdi() string {
   943  	return "vnc"
   944  }
   945  
   946  func (self *SInstance) fetchVMSize() error {
   947  	if self.vmSize == nil {
   948  		vmSize, err := self.host.zone.region.getVMSize(self.Properties.HardwareProfile.VMSize)
   949  		if err != nil {
   950  			return err
   951  		}
   952  		self.vmSize = vmSize
   953  	}
   954  	return nil
   955  }
   956  
   957  func (self *SInstance) GetVcpuCount() int {
   958  	err := self.fetchVMSize()
   959  	if err != nil {
   960  		log.Errorf("fetchVMSize error: %v", err)
   961  		return 0
   962  	}
   963  	return self.vmSize.NumberOfCores
   964  }
   965  
   966  func (self *SInstance) GetVmemSizeMB() int {
   967  	err := self.fetchVMSize()
   968  	if err != nil {
   969  		log.Errorf("fetchVMSize error: %v", err)
   970  		return 0
   971  	}
   972  	return int(self.vmSize.MemoryInMB)
   973  }
   974  
   975  func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
   976  	return nil, cloudprovider.ErrNotSupported
   977  }
   978  
   979  func (self *SRegion) StartVM(instanceId string) error {
   980  	_, err := self.perform(instanceId, "start", nil)
   981  	return err
   982  }
   983  
   984  func (self *SInstance) StartVM(ctx context.Context) error {
   985  	if err := self.host.zone.region.StartVM(self.ID); err != nil {
   986  		return err
   987  	}
   988  	self.host.zone.region.patch(self.ID, jsonutils.Marshal(self))
   989  	return cloudprovider.WaitStatus(self, api.VM_RUNNING, 10*time.Second, 300*time.Second)
   990  }
   991  
   992  func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
   993  	err := self.host.zone.region.StopVM(self.ID, opts.IsForce)
   994  	if err != nil {
   995  		return err
   996  	}
   997  	self.host.zone.region.patch(self.ID, jsonutils.Marshal(self))
   998  	return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 300*time.Second)
   999  }
  1000  
  1001  func (self *SRegion) StopVM(instanceId string, isForce bool) error {
  1002  	_, err := self.perform(instanceId, "deallocate", nil)
  1003  	return err
  1004  }
  1005  
  1006  func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
  1007  	nics, err := self.getNics()
  1008  	if err != nil {
  1009  		return nil, err
  1010  	}
  1011  	for _, nic := range nics {
  1012  		for _, ip := range nic.Properties.IPConfigurations {
  1013  			if ip.Properties.PublicIPAddress != nil && len(ip.Properties.PublicIPAddress.ID) > 0 {
  1014  				eip, err := self.host.zone.region.GetEip(ip.Properties.PublicIPAddress.ID)
  1015  				if err != nil {
  1016  					return nil, errors.Wrapf(err, "GetEip(%s)", ip.Properties.PublicIPAddress.ID)
  1017  				}
  1018  				if len(eip.Properties.IPAddress) > 0 {
  1019  					return eip, nil
  1020  				}
  1021  			}
  1022  		}
  1023  	}
  1024  	return nil, nil
  1025  }
  1026  
  1027  func (self *SInstance) AssignSecurityGroup(secgroupId string) error {
  1028  	return self.host.zone.region.SetSecurityGroup(self.ID, secgroupId)
  1029  }
  1030  
  1031  func (self *SInstance) SetSecurityGroups(secgroupIds []string) error {
  1032  	if len(secgroupIds) == 1 {
  1033  		return self.host.zone.region.SetSecurityGroup(self.ID, secgroupIds[0])
  1034  	}
  1035  	return fmt.Errorf("Unexpect segroup count %d", len(secgroupIds))
  1036  }
  1037  
  1038  func (self *SInstance) GetBillingType() string {
  1039  	return billing_api.BILLING_TYPE_POSTPAID
  1040  }
  1041  
  1042  func (self *SInstance) GetCreatedAt() time.Time {
  1043  	return self.Properties.TimeCreated
  1044  }
  1045  
  1046  func (self *SInstance) GetExpiredAt() time.Time {
  1047  	return time.Time{}
  1048  }
  1049  
  1050  func (self *SInstance) UpdateUserData(userData string) error {
  1051  	return cloudprovider.ErrNotSupported
  1052  }
  1053  
  1054  func (self *SInstance) Renew(bc billing.SBillingCycle) error {
  1055  	return cloudprovider.ErrNotSupported
  1056  }
  1057  
  1058  func (self *SInstance) GetProjectId() string {
  1059  	return getResourceGroup(self.ID)
  1060  }
  1061  
  1062  func (self *SInstance) GetError() error {
  1063  	if self.Properties.InstanceView != nil {
  1064  		for _, status := range self.Properties.InstanceView.Statuses {
  1065  			if status.Code == "ProvisioningState/failed/AllocationFailed" {
  1066  				return errors.Errorf("%s %s", status.Code, status.Message)
  1067  			}
  1068  		}
  1069  	}
  1070  	return nil
  1071  }
  1072  
  1073  func (self *SRegion) SaveImage(osType, diskId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
  1074  	params := map[string]interface{}{
  1075  		"Location": self.Name,
  1076  		"Name":     opts.Name,
  1077  		"Properties": map[string]interface{}{
  1078  			"storageProfile": map[string]interface{}{
  1079  				"osDisk": map[string]interface{}{
  1080  					"osType": osType,
  1081  					"managedDisk": map[string]string{
  1082  						"id": diskId,
  1083  					},
  1084  					"osState": "Generalized",
  1085  				},
  1086  			},
  1087  		},
  1088  		"Type": "Microsoft.Compute/images",
  1089  	}
  1090  	image := &SImage{storageCache: self.getStoragecache()}
  1091  	err := self.create("", jsonutils.Marshal(params), image)
  1092  	if err != nil {
  1093  		return nil, errors.Wrapf(err, "create image")
  1094  	}
  1095  	return image, nil
  1096  }
  1097  
  1098  func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
  1099  	if self.Properties.StorageProfile.OsDisk.ManagedDisk == nil {
  1100  		return nil, fmt.Errorf("invalid os disk for save image")
  1101  	}
  1102  	image, err := self.host.zone.region.SaveImage(string(self.GetOsType()), self.Properties.StorageProfile.OsDisk.ManagedDisk.ID, opts)
  1103  	if err != nil {
  1104  		return nil, errors.Wrapf(err, "SaveImage")
  1105  	}
  1106  	return image, nil
  1107  }
  1108  
  1109  func (self *SInstance) SetTags(tags map[string]string, replace bool) error {
  1110  	if !replace {
  1111  		for k, v := range self.Tags {
  1112  			if _, ok := tags[k]; !ok {
  1113  				tags[k] = v
  1114  			}
  1115  		}
  1116  	}
  1117  	_, err := self.host.zone.region.client.SetTags(self.ID, tags)
  1118  	if err != nil {
  1119  		return errors.Wrapf(err, "self.host.zone.region.client.SetTags(%s,%s)", self.ID, jsonutils.Marshal(tags).String())
  1120  	}
  1121  	return nil
  1122  }