instance.go 34 KB

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