instance.go 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146
  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 huawei
  15. import (
  16. "context"
  17. "fmt"
  18. "net/url"
  19. "sort"
  20. "strconv"
  21. "strings"
  22. "time"
  23. "yunion.io/x/jsonutils"
  24. "yunion.io/x/log"
  25. "yunion.io/x/pkg/errors"
  26. "yunion.io/x/pkg/util/billing"
  27. "yunion.io/x/pkg/util/osprofile"
  28. "yunion.io/x/pkg/utils"
  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. )
  35. const (
  36. InstanceStatusRunning = "ACTIVE"
  37. InstanceStatusTerminated = "DELETED"
  38. InstanceStatusStopped = "SHUTOFF"
  39. )
  40. type IpAddress struct {
  41. Version string `json:"version"`
  42. Addr string `json:"addr"`
  43. OSEXTIPSMACMACAddr string `json:"OS-EXT-IPS-MAC:mac_addr"`
  44. OSEXTIPSPortID string `json:"OS-EXT-IPS:port_id"`
  45. OSEXTIPSType string `json:"OS-EXT-IPS:type"`
  46. }
  47. type Flavor struct {
  48. Disk string `json:"disk"`
  49. Vcpus string `json:"vcpus"`
  50. RAM string `json:"ram"`
  51. ID string `json:"id"`
  52. Name string `json:"name"`
  53. }
  54. type Image struct {
  55. ID string `json:"id"`
  56. }
  57. type VMMetadata struct {
  58. MeteringImageID string `json:"metering.image_id"`
  59. MeteringImagetype string `json:"metering.imagetype"`
  60. MeteringOrderId string `json:"metering.order_id"`
  61. MeteringResourcespeccode string `json:"metering.resourcespeccode"`
  62. ImageName string `json:"image_name"`
  63. OSBit string `json:"os_bit"`
  64. VpcID string `json:"vpc_id"`
  65. MeteringResourcetype string `json:"metering.resourcetype"`
  66. CascadedInstanceExtrainfo string `json:"cascaded.instance_extrainfo"`
  67. OSType string `json:"os_type"`
  68. ChargingMode string `json:"charging_mode"`
  69. }
  70. type OSExtendedVolumesVolumesAttached struct {
  71. Device string `json:"device"`
  72. BootIndex string `json:"bootIndex"`
  73. ID string `json:"id"`
  74. DeleteOnTermination string `json:"delete_on_termination"`
  75. }
  76. type OSSchedulerHints struct {
  77. }
  78. type SecurityGroup struct {
  79. Id string `json:"id"`
  80. Name string `json:"name"`
  81. }
  82. type SysTag struct {
  83. Key string `json:"key"`
  84. Value string `json:"value"`
  85. }
  86. type SInstance struct {
  87. multicloud.SInstanceBase
  88. HuaweiTags
  89. host *SHost
  90. image *SImage
  91. ID string `json:"id"`
  92. Name string `json:"name"`
  93. Addresses map[string][]IpAddress `json:"addresses"`
  94. Flavor Flavor `json:"flavor"`
  95. AccessIPv4 string `json:"accessIPv4"`
  96. AccessIPv6 string `json:"accessIPv6"`
  97. Status string `json:"status"`
  98. Progress string `json:"progress"`
  99. HostID string `json:"hostId"`
  100. Image Image `json:"image"`
  101. Updated string `json:"updated"`
  102. Created time.Time `json:"created"`
  103. Metadata VMMetadata `json:"metadata"`
  104. Description string `json:"description"`
  105. Locked bool `json:"locked"`
  106. ConfigDrive string `json:"config_drive"`
  107. TenantID string `json:"tenant_id"`
  108. UserID string `json:"user_id"`
  109. KeyName string `json:"key_name"`
  110. OSExtendedVolumesVolumesAttached []OSExtendedVolumesVolumesAttached `json:"os-extended-volumes:volumes_attached"`
  111. OSEXTSTSTaskState string `json:"OS-EXT-STS:task_state"`
  112. OSEXTSTSPowerState int64 `json:"OS-EXT-STS:power_state"`
  113. OSEXTSTSVMState string `json:"OS-EXT-STS:vm_state"`
  114. OSEXTSRVATTRHost string `json:"OS-EXT-SRV-ATTR:host"`
  115. OSEXTSRVATTRInstanceName string `json:"OS-EXT-SRV-ATTR:instance_name"`
  116. OSEXTSRVATTRHypervisorHostname string `json:"OS-EXT-SRV-ATTR:hypervisor_hostname"`
  117. OSDCFDiskConfig string `json:"OS-DCF:diskConfig"`
  118. OSEXTAZAvailabilityZone string `json:"OS-EXT-AZ:availability_zone"`
  119. OSSchedulerHints OSSchedulerHints `json:"os:scheduler_hints"`
  120. OSEXTSRVATTRRootDeviceName string `json:"OS-EXT-SRV-ATTR:root_device_name"`
  121. OSEXTSRVATTRRamdiskID string `json:"OS-EXT-SRV-ATTR:ramdisk_id"`
  122. EnterpriseProjectID string `json:"enterprise_project_id"`
  123. OSEXTSRVATTRUserData string `json:"OS-EXT-SRV-ATTR:user_data"`
  124. OSSRVUSGLaunchedAt time.Time `json:"OS-SRV-USG:launched_at"`
  125. OSEXTSRVATTRKernelID string `json:"OS-EXT-SRV-ATTR:kernel_id"`
  126. OSEXTSRVATTRLaunchIndex int64 `json:"OS-EXT-SRV-ATTR:launch_index"`
  127. HostStatus string `json:"host_status"`
  128. OSEXTSRVATTRReservationID string `json:"OS-EXT-SRV-ATTR:reservation_id"`
  129. OSEXTSRVATTRHostname string `json:"OS-EXT-SRV-ATTR:hostname"`
  130. OSSRVUSGTerminatedAt time.Time `json:"OS-SRV-USG:terminated_at"`
  131. SysTags []SysTag `json:"sys_tags"`
  132. SecurityGroups []SecurityGroup `json:"security_groups"`
  133. EnterpriseProjectId string
  134. }
  135. func compareSet(currentSet []string, newSet []string) (add []string, remove []string, keep []string) {
  136. sort.Strings(currentSet)
  137. sort.Strings(newSet)
  138. i, j := 0, 0
  139. for i < len(currentSet) || j < len(newSet) {
  140. if i < len(currentSet) && j < len(newSet) {
  141. if currentSet[i] == newSet[j] {
  142. keep = append(keep, currentSet[i])
  143. i += 1
  144. j += 1
  145. } else if currentSet[i] < newSet[j] {
  146. remove = append(remove, currentSet[i])
  147. i += 1
  148. } else {
  149. add = append(add, newSet[j])
  150. j += 1
  151. }
  152. } else if i >= len(currentSet) {
  153. add = append(add, newSet[j])
  154. j += 1
  155. } else if j >= len(newSet) {
  156. remove = append(remove, currentSet[i])
  157. i += 1
  158. }
  159. }
  160. return add, remove, keep
  161. }
  162. // 启动盘 != 系统盘(必须是启动盘且挂载在root device上)
  163. func isBootDisk(server *SInstance, disk *SDisk) bool {
  164. if disk.GetDiskType() != api.DISK_TYPE_SYS {
  165. return false
  166. }
  167. for _, attachment := range disk.Attachments {
  168. if attachment.ServerID == server.GetId() && attachment.Device == server.OSEXTSRVATTRRootDeviceName {
  169. return true
  170. }
  171. }
  172. return false
  173. }
  174. func (self *SInstance) GetId() string {
  175. return self.ID
  176. }
  177. func (self *SInstance) GetHostname() string {
  178. return self.OSEXTSRVATTRHostname
  179. }
  180. func (self *SInstance) GetName() string {
  181. return self.Name
  182. }
  183. func (self *SInstance) GetGlobalId() string {
  184. return self.ID
  185. }
  186. func (self *SInstance) GetStatus() string {
  187. switch self.Status {
  188. case "ACTIVE":
  189. return api.VM_RUNNING
  190. case "MIGRATING", "REBUILD", "BUILD", "RESIZE", "VERIFY_RESIZE": // todo: pending ?
  191. return api.VM_STARTING
  192. case "REBOOT", "HARD_REBOOT":
  193. return api.VM_STOPPING
  194. case "SHUTOFF":
  195. return api.VM_READY
  196. default:
  197. return api.VM_UNKNOWN
  198. }
  199. }
  200. func (ins *SInstance) GetPowerStates() string {
  201. switch ins.OSEXTSTSPowerState {
  202. case 1:
  203. return api.VM_POWER_STATES_ON
  204. default:
  205. return api.VM_POWER_STATES_OFF
  206. }
  207. }
  208. func (self *SInstance) Refresh() error {
  209. vm, err := self.host.zone.region.GetInstance(self.GetId())
  210. if err != nil {
  211. return err
  212. }
  213. self.OSExtendedVolumesVolumesAttached = nil
  214. self.SecurityGroups = nil
  215. return jsonutils.Update(self, vm)
  216. }
  217. func (self *SInstance) GetInstanceType() string {
  218. return self.Flavor.ID
  219. }
  220. func (self *SInstance) GetSecurityGroupIds() ([]string, error) {
  221. ret := []string{}
  222. for _, sec := range self.SecurityGroups {
  223. ret = append(ret, sec.Id)
  224. }
  225. return ret, nil
  226. }
  227. // key 相同时value不会替换
  228. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=BatchCreateServerTags
  229. func (self *SRegion) CreateServerTags(instanceId string, tags map[string]string) error {
  230. params := map[string]interface{}{
  231. "action": "create",
  232. }
  233. tagsObj := []map[string]string{}
  234. for k, v := range tags {
  235. tagsObj = append(tagsObj, map[string]string{"key": k, "value": v})
  236. }
  237. params["tags"] = tagsObj
  238. _, err := self.post(SERVICE_ECS, fmt.Sprintf("cloudservers/%s/tags/action", instanceId), params)
  239. return err
  240. }
  241. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=BatchDeleteServerTags
  242. func (self *SRegion) DeleteServerTags(instanceId string, tagsKey []string) error {
  243. params := map[string]interface{}{
  244. "action": "delete",
  245. }
  246. tagsObj := []map[string]string{}
  247. for _, k := range tagsKey {
  248. tagsObj = append(tagsObj, map[string]string{"key": k})
  249. }
  250. params["tags"] = tagsObj
  251. _, err := self.post(SERVICE_ECS, fmt.Sprintf("cloudservers/%s/tags/action", instanceId), params)
  252. return err
  253. }
  254. func (self *SInstance) SetTags(tags map[string]string, replace bool) error {
  255. existedTags, err := self.GetTags()
  256. if err != nil {
  257. return errors.Wrap(err, "self.GetTags()")
  258. }
  259. deleteTagsKey := []string{}
  260. for k := range existedTags {
  261. if replace {
  262. deleteTagsKey = append(deleteTagsKey, k)
  263. } else {
  264. if _, ok := tags[k]; ok {
  265. deleteTagsKey = append(deleteTagsKey, k)
  266. }
  267. }
  268. }
  269. if len(deleteTagsKey) > 0 {
  270. err := self.host.zone.region.DeleteServerTags(self.GetId(), deleteTagsKey)
  271. if err != nil {
  272. return errors.Wrapf(err, "DeleteServerTags(%s,%s)", self.GetId(), deleteTagsKey)
  273. }
  274. }
  275. if len(tags) > 0 {
  276. err := self.host.zone.region.CreateServerTags(self.GetId(), tags)
  277. if err != nil {
  278. return errors.Wrapf(err, "CreateServerTags(%s,%s)", self.GetId(), jsonutils.Marshal(tags).String())
  279. }
  280. }
  281. return nil
  282. }
  283. func (self *SInstance) GetBillingType() string {
  284. if self.Metadata.ChargingMode == "1" {
  285. return billing_api.BILLING_TYPE_PREPAID
  286. }
  287. return billing_api.BILLING_TYPE_POSTPAID
  288. }
  289. func (self *SInstance) GetCreatedAt() time.Time {
  290. return self.Created
  291. }
  292. func (self *SInstance) GetDescription() string {
  293. return self.Description
  294. }
  295. func (self *SInstance) GetExpiredAt() time.Time {
  296. orders, err := self.host.zone.region.client.GetOrderResources()
  297. if err != nil {
  298. return time.Time{}
  299. }
  300. order, ok := orders[self.ID]
  301. if ok {
  302. return order.ExpireTime
  303. }
  304. return time.Time{}
  305. }
  306. func (self *SInstance) GetIHost() cloudprovider.ICloudHost {
  307. return self.host
  308. }
  309. func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
  310. attached := self.OSExtendedVolumesVolumesAttached
  311. disks := make([]SDisk, 0)
  312. for _, vol := range attached {
  313. disk, err := self.host.zone.region.GetDisk(vol.ID)
  314. if err != nil {
  315. return nil, err
  316. }
  317. disks = append(disks, *disk)
  318. }
  319. idisks := make([]cloudprovider.ICloudDisk, len(disks))
  320. for i := 0; i < len(disks); i += 1 {
  321. storage, err := self.host.zone.getStorageByCategory(disks[i].VolumeType)
  322. if err != nil {
  323. return nil, err
  324. }
  325. disks[i].storage = storage
  326. idisks[i] = &disks[i]
  327. // 将系统盘放到第0个位置
  328. if isBootDisk(self, &disks[i]) {
  329. _temp := idisks[0]
  330. idisks[0] = &disks[i]
  331. idisks[i] = _temp
  332. }
  333. }
  334. return idisks, nil
  335. }
  336. func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
  337. ret := map[string]*SInstanceNic{}
  338. for _, ipAddresses := range self.Addresses {
  339. for _, ipAddress := range ipAddresses {
  340. if ipAddress.OSEXTIPSType == "fixed" && ipAddress.Version == "4" {
  341. _, ok := ret[ipAddress.OSEXTIPSMACMACAddr]
  342. if !ok {
  343. ret[ipAddress.OSEXTIPSMACMACAddr] = &SInstanceNic{
  344. instance: self,
  345. ipAddr: ipAddress.Addr,
  346. macAddr: ipAddress.OSEXTIPSMACMACAddr,
  347. subAddrs: []string{},
  348. }
  349. } else {
  350. addrs := append(ret[ipAddress.OSEXTIPSMACMACAddr].subAddrs, ipAddress.Addr)
  351. ret[ipAddress.OSEXTIPSMACMACAddr].subAddrs = addrs
  352. }
  353. }
  354. }
  355. }
  356. for _, ipAddresses := range self.Addresses {
  357. for _, ipAddress := range ipAddresses {
  358. if ipAddress.OSEXTIPSType == "fixed" && ipAddress.Version == "6" {
  359. if _, ok := ret[ipAddress.OSEXTIPSMACMACAddr]; ok {
  360. ret[ipAddress.OSEXTIPSMACMACAddr].ip6Addr = ipAddress.Addr
  361. }
  362. }
  363. }
  364. }
  365. nics := make([]cloudprovider.ICloudNic, 0)
  366. for mac := range ret {
  367. log.Errorf("mac: %s ip: %s ipv6: %s", ret[mac].macAddr, ret[mac].ipAddr, ret[mac].ip6Addr)
  368. nics = append(nics, ret[mac])
  369. }
  370. return nics, nil
  371. }
  372. func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
  373. ips := make([]string, 0)
  374. for _, addresses := range self.Addresses {
  375. for _, address := range addresses {
  376. if address.OSEXTIPSType != "fixed" && !strings.HasPrefix(address.Addr, "100.") && address.Version == "4" {
  377. ips = append(ips, address.Addr)
  378. }
  379. }
  380. }
  381. if len(ips) == 0 {
  382. return nil, nil
  383. }
  384. eips, err := self.host.zone.region.GetEips("", ips)
  385. if err != nil {
  386. return nil, err
  387. }
  388. if len(eips) > 0 {
  389. return &eips[0], nil
  390. }
  391. return nil, nil
  392. }
  393. func (self *SInstance) GetVcpuCount() int {
  394. cpu, _ := strconv.Atoi(self.Flavor.Vcpus)
  395. return cpu
  396. }
  397. func (self *SInstance) GetVmemSizeMB() int {
  398. mem, _ := strconv.Atoi(self.Flavor.RAM)
  399. return int(mem)
  400. }
  401. func (self *SInstance) GetBootOrder() string {
  402. return "dcn"
  403. }
  404. func (self *SInstance) GetVga() string {
  405. return "std"
  406. }
  407. func (self *SInstance) GetVdi() string {
  408. return "vnc"
  409. }
  410. func (i *SInstance) getImage() *SImage {
  411. if i.image == nil && len(i.Image.ID) > 0 {
  412. image, err := i.host.zone.region.GetImage(i.Image.ID)
  413. if err == nil {
  414. i.image = image
  415. } else {
  416. log.Debugf("GetOSArch.GetImage %s: %s", i.Image.ID, err)
  417. }
  418. }
  419. return i.image
  420. }
  421. func (self *SInstance) GetOsArch() string {
  422. img := self.getImage()
  423. if img != nil {
  424. return img.GetOsArch()
  425. }
  426. t := self.GetInstanceType()
  427. if len(t) > 0 {
  428. if strings.HasPrefix(t, "k") {
  429. return apis.OS_ARCH_AARCH64
  430. }
  431. }
  432. return apis.OS_ARCH_X86
  433. }
  434. func (self *SInstance) GetOsType() cloudprovider.TOsType {
  435. return cloudprovider.TOsType(osprofile.NormalizeOSType(self.Metadata.OSType))
  436. }
  437. func (self *SInstance) GetFullOsName() string {
  438. return self.Metadata.ImageName
  439. }
  440. func (self *SInstance) GetBios() cloudprovider.TBiosType {
  441. img := self.getImage()
  442. if img != nil {
  443. return img.GetBios()
  444. }
  445. return cloudprovider.BIOS
  446. }
  447. func (self *SInstance) GetOsDist() string {
  448. img := self.getImage()
  449. if img != nil {
  450. return img.GetOsDist()
  451. }
  452. return ""
  453. }
  454. func (self *SInstance) GetOsVersion() string {
  455. img := self.getImage()
  456. if img != nil {
  457. return img.GetOsVersion()
  458. }
  459. return ""
  460. }
  461. func (self *SInstance) GetOsLang() string {
  462. img := self.getImage()
  463. if img != nil {
  464. return img.GetOsLang()
  465. }
  466. return ""
  467. }
  468. func (self *SInstance) GetMachine() string {
  469. return "pc"
  470. }
  471. func (self *SInstance) SetSecurityGroups(secgroupIds []string) error {
  472. ids, err := self.GetSecurityGroupIds()
  473. if err != nil {
  474. return err
  475. }
  476. add, remove, _ := compareSet(ids, secgroupIds)
  477. for _, id := range add {
  478. err = self.host.zone.region.assignSecurityGroup(self.GetId(), id)
  479. if err != nil {
  480. return err
  481. }
  482. }
  483. for _, id := range remove {
  484. err := self.host.zone.region.unassignSecurityGroups(self.GetId(), id)
  485. if err != nil {
  486. return err
  487. }
  488. }
  489. return nil
  490. }
  491. func (self *SInstance) GetHypervisor() string {
  492. return api.HYPERVISOR_HUAWEI
  493. }
  494. func (self *SInstance) StartVM(ctx context.Context) error {
  495. return self.host.zone.region.StartVM(self.GetId())
  496. }
  497. func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
  498. return self.host.zone.region.StopVM(self.GetId(), opts.IsForce)
  499. }
  500. func (self *SInstance) DeleteVM(ctx context.Context) error {
  501. if self.GetBillingType() == billing_api.BILLING_TYPE_PREPAID {
  502. return self.host.zone.region.CancelResourcesSubscription([]string{self.ID})
  503. }
  504. return self.host.zone.region.DeleteVM(self.GetId())
  505. }
  506. func (self *SRegion) CancelResourcesSubscription(resourceIds []string) error {
  507. params := map[string]interface{}{
  508. "resource_ids": resourceIds,
  509. "unsubscribe_type": 1,
  510. "unsubscribe_reason_type": 5,
  511. "unsubscribe_reason": "cloudpods auto delete prepaid resources",
  512. }
  513. _, err := self.post(SERVICE_BSS, "orders/subscriptions/resources/unsubscribe", params)
  514. return err
  515. }
  516. func (self *SInstance) UpdateVM(ctx context.Context, input cloudprovider.SInstanceUpdateOptions) error {
  517. return self.host.zone.region.UpdateVM(self.GetId(), input)
  518. }
  519. func (self *SInstance) UpdateUserData(userData string) error {
  520. return cloudprovider.ErrNotSupported
  521. }
  522. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=ChangeServerOsWithoutCloudInit
  523. func (self *SRegion) RebuildRoot(instanceId string, opts *cloudprovider.SManagedVMRebuildRootConfig) (jsonutils.JSONObject, error) {
  524. var err error
  525. keyName := ""
  526. if len(opts.PublicKey) > 0 {
  527. keyName, err = self.syncKeypair(opts.PublicKey)
  528. if err != nil {
  529. return nil, err
  530. }
  531. }
  532. params := map[string]interface{}{
  533. "imageid": opts.ImageId,
  534. }
  535. if len(opts.Password) > 0 {
  536. params["adminpass"] = opts.Password
  537. }
  538. if len(keyName) > 0 {
  539. params["keyname"] = keyName
  540. }
  541. return self.post(SERVICE_ECS, fmt.Sprintf("cloudservers/%s/changeos", instanceId), map[string]interface{}{"os-change": params})
  542. }
  543. func (self *SInstance) RebuildRoot(ctx context.Context, opts *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
  544. resp, err := self.host.zone.region.RebuildRoot(self.ID, opts)
  545. if err != nil {
  546. return "", errors.Wrapf(err, "change os")
  547. }
  548. jobId, err := resp.GetString("job_id")
  549. if err != nil {
  550. return "", errors.Wrapf(err, "get job_id")
  551. }
  552. err = self.host.zone.region.waitTaskStatus(SERVICE_ECS, jobId, TASK_SUCCESS, 15*time.Second, 900*time.Second)
  553. if err != nil {
  554. return "", errors.Wrapf(err, "wait task")
  555. }
  556. err = self.Refresh()
  557. if err != nil {
  558. return "", err
  559. }
  560. idisks, err := self.GetIDisks()
  561. if err != nil {
  562. return "", err
  563. }
  564. if len(idisks) == 0 {
  565. return "", fmt.Errorf("server %s has no volume attached.", self.GetId())
  566. }
  567. return idisks[0].GetId(), nil
  568. }
  569. func (self *SInstance) DeployVM(ctx context.Context, opts *cloudprovider.SInstanceDeployOptions) error {
  570. return self.host.zone.region.DeployVM(self.GetId(), opts)
  571. }
  572. func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
  573. instanceTypes := []string{}
  574. if len(config.InstanceType) > 0 {
  575. instanceTypes = []string{config.InstanceType}
  576. } else {
  577. flavors, err := self.host.zone.region.GetMatchInstanceTypes(config.Cpu, config.MemoryMB, self.OSEXTAZAvailabilityZone)
  578. if err != nil {
  579. return errors.Wrapf(err, "GetMatchInstanceTypes")
  580. }
  581. for _, flavor := range flavors {
  582. instanceTypes = append(instanceTypes, flavor.ID)
  583. }
  584. }
  585. var err error
  586. for _, instanceType := range instanceTypes {
  587. err = self.host.zone.region.ChangeVMConfig(self.GetId(), instanceType)
  588. if err != nil {
  589. log.Warningf("ChangeVMConfig %s for %s error: %v", self.GetId(), instanceType, err)
  590. } else {
  591. return cloudprovider.WaitStatusWithDelay(self, api.VM_READY, 15*time.Second, 15*time.Second, 180*time.Second)
  592. }
  593. }
  594. if err != nil {
  595. return errors.Wrapf(err, "ChangeVMConfig")
  596. }
  597. return fmt.Errorf("Failed to change vm config, specification not supported")
  598. }
  599. func (self *SInstance) GetModificationTypes() ([]cloudprovider.SInstanceModificationType, error) {
  600. return self.host.zone.region.GetInstanceModificationTypes(self.GetId())
  601. }
  602. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=ListResizeFlavors
  603. func (self *SRegion) GetInstanceModificationTypes(instanceId string) ([]cloudprovider.SInstanceModificationType, error) {
  604. params := url.Values{}
  605. params.Set("instance_uuid", instanceId)
  606. resp, err := self.list(SERVICE_ECS, "cloudservers/resize_flavors", params)
  607. if err != nil {
  608. return nil, errors.Wrapf(err, "ListResizeFlavors(%s)", instanceId)
  609. }
  610. flavors := []SInstanceType{}
  611. err = resp.Unmarshal(&flavors, "flavors")
  612. if err != nil {
  613. return nil, errors.Wrapf(err, "resp.Unmarshal(flavors)")
  614. }
  615. ret := make([]cloudprovider.SInstanceModificationType, 0, len(flavors))
  616. for _, flavor := range flavors {
  617. ret = append(ret, cloudprovider.SInstanceModificationType{InstanceType: flavor.ID})
  618. }
  619. return ret, nil
  620. }
  621. func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
  622. return self.host.zone.region.GetInstanceVNCUrl(self.GetId())
  623. }
  624. func (self *SInstance) NextDeviceName() (string, error) {
  625. prefix := "s"
  626. if strings.Contains(self.OSEXTSRVATTRRootDeviceName, "/vd") {
  627. prefix = "v"
  628. }
  629. currents := []string{}
  630. for _, item := range self.OSExtendedVolumesVolumesAttached {
  631. currents = append(currents, strings.ToLower(item.Device))
  632. }
  633. for i := 0; i < 25; i++ {
  634. device := fmt.Sprintf("/dev/%sd%s", prefix, string([]byte{byte(98 + i)}))
  635. if ok, _ := utils.InStringArray(device, currents); !ok {
  636. return device, nil
  637. }
  638. }
  639. return "", fmt.Errorf("disk devicename out of index, current deivces: %s", currents)
  640. }
  641. func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
  642. device, err := self.NextDeviceName()
  643. if err != nil {
  644. return errors.Wrap(err, "NextDeviceName")
  645. }
  646. return self.host.zone.region.AttachDisk(self.GetId(), diskId, device)
  647. }
  648. func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
  649. for _, disk := range self.OSExtendedVolumesVolumesAttached {
  650. if disk.ID == diskId {
  651. return self.host.zone.region.DetachDisk(self.GetId(), diskId)
  652. }
  653. }
  654. return nil
  655. }
  656. func (self *SInstance) Renew(bc billing.SBillingCycle) error {
  657. return self.host.zone.region.RenewInstance(self.GetId(), bc)
  658. }
  659. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=ListServersDetails
  660. func (self *SRegion) GetInstances(ip string) ([]SInstance, error) {
  661. params := url.Values{}
  662. if len(ip) > 0 {
  663. params.Set("ip", ip)
  664. }
  665. ret := []SInstance{}
  666. for {
  667. resp, err := self.list(SERVICE_ECS, "cloudservers/detail", params)
  668. if err != nil {
  669. return nil, err
  670. }
  671. part := struct {
  672. Servers []SInstance
  673. }{}
  674. err = resp.Unmarshal(&part)
  675. if err != nil {
  676. return nil, err
  677. }
  678. ret = append(ret, part.Servers...)
  679. if len(part.Servers) == 0 {
  680. break
  681. }
  682. params.Set("marker", part.Servers[len(part.Servers)-1].ID)
  683. }
  684. return ret, nil
  685. }
  686. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=ShowServer
  687. func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) {
  688. resp, err := self.list(SERVICE_ECS, "cloudservers/"+instanceId, nil)
  689. if err != nil {
  690. return nil, err
  691. }
  692. ret := &SInstance{}
  693. err = resp.Unmarshal(ret, "server")
  694. if err != nil {
  695. return nil, err
  696. }
  697. // 华为云删除的实例依然可以查到
  698. if ret.Status == "DELETED" {
  699. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "aleady deleted")
  700. }
  701. return ret, nil
  702. }
  703. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=CreateServers
  704. func (self *SRegion) CreateInstance(keypair, zoneId string, opts *cloudprovider.SManagedVMCreateConfig) (string, error) {
  705. secgroups := []map[string]interface{}{}
  706. for _, id := range opts.ExternalSecgroupIds {
  707. secgroups = append(secgroups, map[string]interface{}{
  708. "id": id,
  709. })
  710. }
  711. dataDisks := []map[string]interface{}{}
  712. for _, disk := range opts.DataDisks {
  713. dataDisks = append(dataDisks, map[string]interface{}{
  714. "volumetype": disk.StorageType,
  715. "size": disk.SizeGB,
  716. })
  717. }
  718. extendparam := map[string]interface{}{
  719. "regionID": self.getId(),
  720. "chargingMode": POST_PAID,
  721. }
  722. if len(opts.ProjectId) > 0 {
  723. extendparam["enterprise_project_id"] = opts.ProjectId
  724. }
  725. // billing type
  726. if opts.BillingCycle != nil {
  727. extendparam["chargingMode"] = PRE_PAID
  728. extendparam["isAutoRenew"] = opts.BillingCycle.AutoRenew
  729. extendparam["isAutoPay"] = true
  730. if opts.BillingCycle.GetMonths() <= 9 {
  731. extendparam["periodNum"] = opts.BillingCycle.GetMonths()
  732. extendparam["periodType"] = "month"
  733. } else {
  734. extendparam["periodNum"] = opts.BillingCycle.GetYears()
  735. extendparam["periodType"] = "year"
  736. }
  737. }
  738. params := map[string]interface{}{
  739. "availability_zone": zoneId,
  740. "name": opts.Name,
  741. "flavorRef": opts.InstanceType,
  742. "imageRef": opts.ExternalImageId,
  743. "count": 1,
  744. "nics": []map[string]interface{}{
  745. {
  746. "subnet_id": opts.ExternalNetworkId,
  747. "ip_address": opts.IpAddr,
  748. },
  749. },
  750. "security_groups": secgroups,
  751. "vpcid": opts.ExternalVpcId,
  752. "root_volume": map[string]interface{}{
  753. "volumetype": opts.SysDisk.StorageType,
  754. "size": opts.SysDisk.SizeGB,
  755. },
  756. "data_volumes": dataDisks,
  757. "extendparam": extendparam,
  758. }
  759. if len(keypair) > 0 {
  760. params["key_name"] = keypair
  761. } else {
  762. params["adminPass"] = opts.Password
  763. }
  764. if len(opts.UserData) > 0 {
  765. params["user_data"] = opts.UserData
  766. }
  767. if len(opts.Tags) > 0 {
  768. serverTags := []map[string]interface{}{}
  769. for k, v := range opts.Tags {
  770. serverTags = append(serverTags, map[string]interface{}{
  771. "key": k,
  772. "value": v,
  773. })
  774. }
  775. params["server_tags"] = serverTags
  776. }
  777. resp, err := self.post(SERVICE_ECS_V1_1, "cloudservers", map[string]interface{}{"server": params})
  778. if err != nil {
  779. return "", err
  780. }
  781. ret := struct {
  782. JobId string
  783. OrderId string
  784. ServerIds []string `json:"serverIds"`
  785. }{}
  786. err = resp.Unmarshal(&ret)
  787. if err != nil {
  788. return "", err
  789. }
  790. for _, id := range ret.ServerIds {
  791. return id, nil
  792. }
  793. if len(ret.JobId) > 0 {
  794. ids, err := self.GetAllSubTaskEntityIDs(SERVICE_ECS, ret.JobId)
  795. if err != nil {
  796. return "", errors.Wrapf(err, "GetAllSubTaskEntityIDs(%s)", ret.JobId)
  797. }
  798. for _, id := range ids {
  799. return id, nil
  800. }
  801. }
  802. return "", fmt.Errorf("create server return empyte response %s", resp.String())
  803. }
  804. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=NovaAssociateSecurityGroup
  805. func (self *SRegion) assignSecurityGroup(instanceId, secgroupId string) error {
  806. _, err := self.post(SERVICE_ECS_V2_1, fmt.Sprintf("servers/%s/action", instanceId), map[string]interface{}{
  807. "addSecurityGroup": map[string]interface{}{
  808. "name": secgroupId,
  809. },
  810. })
  811. return err
  812. }
  813. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=NovaDisassociateSecurityGroup
  814. func (self *SRegion) unassignSecurityGroups(instanceId, secgroupId string) error {
  815. _, err := self.post(SERVICE_ECS_V2_1, fmt.Sprintf("servers/%s/action", instanceId), map[string]interface{}{
  816. "removeSecurityGroup": map[string]interface{}{
  817. "name": secgroupId,
  818. },
  819. })
  820. return err
  821. }
  822. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=NovaStartServer
  823. func (self *SRegion) StartVM(instanceId string) error {
  824. params := map[string]interface{}{
  825. "os-start": map[string]string{},
  826. }
  827. _, err := self.post(SERVICE_ECS_V2_1, fmt.Sprintf("servers/%s/action", instanceId), params)
  828. return err
  829. }
  830. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=NovaStopServer
  831. func (self *SRegion) StopVM(instanceId string, isForce bool) error {
  832. stopType := "SOFT"
  833. if isForce {
  834. stopType = "HARD"
  835. }
  836. params := map[string]interface{}{
  837. "os-stop": map[string]string{
  838. "type": stopType,
  839. },
  840. }
  841. _, err := self.post(SERVICE_ECS_V2_1, fmt.Sprintf("servers/%s/action", instanceId), params)
  842. return err
  843. }
  844. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=DeleteServers
  845. func (self *SRegion) DeleteVM(instanceId string) error {
  846. params := map[string]interface{}{
  847. "servers": []map[string]interface{}{
  848. {
  849. "id": instanceId,
  850. },
  851. },
  852. }
  853. _, err := self.post(SERVICE_ECS, "cloudservers/delete", params)
  854. return err
  855. }
  856. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=UpdateServer
  857. func (self *SRegion) UpdateVM(instanceId string, input cloudprovider.SInstanceUpdateOptions) error {
  858. server := map[string]interface{}{
  859. "name": input.NAME,
  860. }
  861. if len(input.HostName) > 0 {
  862. server["hostname"] = input.HostName
  863. }
  864. if len(input.Description) > 0 {
  865. server["description"] = input.Description
  866. }
  867. params := map[string]interface{}{
  868. "server": server,
  869. }
  870. _, err := self.put(SERVICE_ECS, "cloudservers/"+instanceId, params)
  871. return err
  872. }
  873. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=BatchResetServersPassword
  874. func (self *SRegion) BatchResetServersPassword(instanceId string, password string) error {
  875. params := map[string]interface{}{
  876. "new_password": password,
  877. "servers": []map[string]interface{}{
  878. {
  879. "id": instanceId,
  880. },
  881. },
  882. }
  883. _, err := self.put(SERVICE_ECS, "cloudservers/os-reset-passwords", params)
  884. if err != nil {
  885. return errors.Wrapf(err, "reset password")
  886. }
  887. return nil
  888. }
  889. func (self *SRegion) DeployVM(instanceId string, opts *cloudprovider.SInstanceDeployOptions) error {
  890. if len(opts.Password) > 0 {
  891. err := self.BatchResetServersPassword(instanceId, opts.Password)
  892. if err != nil {
  893. return errors.Wrapf(err, "BatchResetServersPassword")
  894. }
  895. }
  896. return nil
  897. }
  898. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=ResizeServer
  899. func (self *SRegion) ChangeVMConfig(instanceId string, instanceType string) error {
  900. params := map[string]interface{}{
  901. "resize": map[string]interface{}{
  902. "flavorRef": instanceType,
  903. },
  904. }
  905. _, err := self.post(SERVICE_ECS_V1_1, fmt.Sprintf("cloudservers/%s/resize", instanceId), params)
  906. return err
  907. }
  908. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=ShowServerRemoteConsole
  909. func (self *SRegion) GetInstanceVNCUrl(instanceId string) (*cloudprovider.ServerVncOutput, error) {
  910. params := map[string]interface{}{
  911. "remote_console": map[string]interface{}{
  912. "type": "novnc",
  913. "protocol": "vnc",
  914. },
  915. }
  916. resp, err := self.post(SERVICE_ECS, fmt.Sprintf("cloudservers/%s/remote_console", instanceId), params)
  917. if err != nil {
  918. return nil, err
  919. }
  920. result := &cloudprovider.ServerVncOutput{
  921. Hypervisor: api.HYPERVISOR_HUAWEI,
  922. }
  923. resp.Unmarshal(result, "remote_console")
  924. result.Protocol = api.HYPERVISOR_HUAWEI
  925. return result, nil
  926. }
  927. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=AttachServerVolume
  928. func (self *SRegion) AttachDisk(instanceId string, diskId string, device string) error {
  929. params := map[string]interface{}{
  930. "volumeAttachment": map[string]interface{}{
  931. "volumeId": diskId,
  932. "device": device,
  933. },
  934. }
  935. _, err := self.post(SERVICE_ECS, fmt.Sprintf("cloudservers/%s/attachvolume", instanceId), params)
  936. return err
  937. }
  938. // https://console.huaweicloud.com/apiexplorer/#/openapi/ECS/doc?api=DetachServerVolume
  939. func (self *SRegion) DetachDisk(instanceId string, diskId string) error {
  940. _, err := self.delete(SERVICE_ECS, fmt.Sprintf("cloudservers/%s/detachvolume/%s", instanceId, diskId))
  941. //volume a2091934-2669-4fca-8eb4-a950c1836b3c is not in server 49b053d2-f798-432f-af55-76eb6ef2c769 attach volume list => 磁盘已经被卸载了
  942. if err != nil && strings.Contains(err.Error(), fmt.Sprintf("is not in server")) && strings.Contains(err.Error(), fmt.Sprintf("attach volume list")) {
  943. return nil
  944. }
  945. return err
  946. }
  947. // https://console.huaweicloud.com/apiexplorer/#/openapi/BSS/doc?api=RenewalResources
  948. func (self *SRegion) RenewInstance(instanceId string, bc billing.SBillingCycle) error {
  949. params := map[string]interface{}{
  950. "resource_ids": []string{instanceId},
  951. "expire_policy": 3,
  952. "is_auto_pay": 1,
  953. }
  954. month := int64(bc.GetMonths())
  955. year := int64(bc.GetYears())
  956. if month >= 1 && month <= 11 {
  957. params["period_type"] = 2
  958. params["period_num"] = month
  959. } else if year >= 1 && year <= 3 {
  960. params["period_type"] = 3
  961. params["period_num"] = year
  962. } else {
  963. return fmt.Errorf("invalid renew period %d month,must be 1~11 month or 1~3 year", month)
  964. }
  965. _, err := self.post(SERVICE_BSS, "orders/subscriptions/resources/renew", params)
  966. return err
  967. }
  968. func (self *SInstance) GetProjectId() string {
  969. return self.EnterpriseProjectId
  970. }
  971. func (self *SInstance) GetError() error {
  972. return nil
  973. }
  974. // https://console.huaweicloud.com/apiexplorer/#/openapi/IMS/doc?api=CreateImage
  975. func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
  976. params := map[string]interface{}{
  977. "name": opts.Name,
  978. "instance_id": instanceId,
  979. }
  980. if len(opts.Notes) > 0 {
  981. params["description"] = func() string {
  982. opts.Notes = strings.ReplaceAll(opts.Notes, "<", "")
  983. opts.Notes = strings.ReplaceAll(opts.Notes, ">", "")
  984. opts.Notes = strings.ReplaceAll(opts.Notes, "\n", "")
  985. if len(opts.Notes) > 1024 {
  986. opts.Notes = opts.Notes[:1024]
  987. }
  988. return opts.Notes
  989. }()
  990. }
  991. resp, err := self.post(SERVICE_IMS, "cloudimages/action", params)
  992. if err != nil {
  993. return nil, errors.Wrapf(err, "crate image")
  994. }
  995. jobId, err := resp.GetString("job_id")
  996. if err != nil {
  997. return nil, errors.Wrapf(err, "get job_id")
  998. }
  999. err = self.waitTaskStatus(SERVICE_IMS_V1, jobId, TASK_SUCCESS, 15*time.Second, 10*time.Minute)
  1000. if err != nil {
  1001. return nil, errors.Wrapf(err, "waitTaskStatus")
  1002. }
  1003. imageId, err := self.GetTaskEntityID(SERVICE_IMS, jobId, "image_id")
  1004. if err != nil {
  1005. return nil, errors.Wrapf(err, "GetTaskEntityID")
  1006. }
  1007. image, err := self.GetImage(imageId)
  1008. if err != nil {
  1009. return nil, errors.Wrapf(err, "GetImage(%s)", imageId)
  1010. }
  1011. image.storageCache = self.getStoragecache()
  1012. return image, nil
  1013. }
  1014. func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
  1015. image, err := self.host.zone.region.SaveImage(self.ID, opts)
  1016. if err != nil {
  1017. return nil, errors.Wrapf(err, "SaveImage")
  1018. }
  1019. return image, nil
  1020. }