instance.go 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443
  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 hcso
  15. import (
  16. "context"
  17. "encoding/base64"
  18. "fmt"
  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/cloudinit"
  28. "yunion.io/x/pkg/util/imagetools"
  29. "yunion.io/x/pkg/util/osprofile"
  30. "yunion.io/x/pkg/utils"
  31. "yunion.io/x/cloudmux/pkg/apis"
  32. billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
  33. api "yunion.io/x/cloudmux/pkg/apis/compute"
  34. "yunion.io/x/cloudmux/pkg/cloudprovider"
  35. "yunion.io/x/cloudmux/pkg/multicloud"
  36. "yunion.io/x/cloudmux/pkg/multicloud/huawei"
  37. )
  38. const (
  39. InstanceStatusRunning = "ACTIVE"
  40. InstanceStatusTerminated = "DELETED"
  41. InstanceStatusStopped = "SHUTOFF"
  42. )
  43. type IpAddress struct {
  44. Version string `json:"version"`
  45. Addr string `json:"addr"`
  46. OSEXTIPSMACMACAddr string `json:"OS-EXT-IPS-MAC:mac_addr"`
  47. OSEXTIPSPortID string `json:"OS-EXT-IPS:port_id"`
  48. OSEXTIPSType string `json:"OS-EXT-IPS:type"`
  49. }
  50. type Flavor struct {
  51. Disk string `json:"disk"`
  52. Vcpus string `json:"vcpus"`
  53. RAM string `json:"ram"`
  54. ID string `json:"id"`
  55. Name string `json:"name"`
  56. }
  57. type Image struct {
  58. ID string `json:"id"`
  59. }
  60. type VMMetadata struct {
  61. MeteringImageID string `json:"metering.image_id"`
  62. MeteringImagetype string `json:"metering.imagetype"`
  63. MeteringResourcespeccode string `json:"metering.resourcespeccode"`
  64. ImageName string `json:"image_name"`
  65. OSBit string `json:"os_bit"`
  66. VpcID string `json:"vpc_id"`
  67. MeteringResourcetype string `json:"metering.resourcetype"`
  68. CascadedInstanceExtrainfo string `json:"cascaded.instance_extrainfo"`
  69. OSType string `json:"os_type"`
  70. ChargingMode string `json:"charging_mode"`
  71. }
  72. type OSExtendedVolumesVolumesAttached struct {
  73. Device string `json:"device"`
  74. BootIndex string `json:"bootIndex"`
  75. ID string `json:"id"`
  76. DeleteOnTermination string `json:"delete_on_termination"`
  77. }
  78. type OSSchedulerHints struct {
  79. }
  80. type SecurityGroup struct {
  81. Name string `json:"name"`
  82. }
  83. type SysTag struct {
  84. Key string `json:"key"`
  85. Value string `json:"value"`
  86. }
  87. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148849.html
  88. // https://support.huaweicloud.com/api-bpconsole/zh-cn_topic_0100166287.html v1.1 支持创建包年/包月的弹性云服务器
  89. type SInstance struct {
  90. multicloud.SInstanceBase
  91. huawei.HuaweiTags
  92. host *SHost
  93. osInfo *imagetools.ImageInfo
  94. ID string `json:"id"`
  95. Name string `json:"name"`
  96. Addresses map[string][]IpAddress `json:"addresses"`
  97. Flavor Flavor `json:"flavor"`
  98. AccessIPv4 string `json:"accessIPv4"`
  99. AccessIPv6 string `json:"accessIPv6"`
  100. Status string `json:"status"`
  101. Progress string `json:"progress"`
  102. HostID string `json:"hostId"`
  103. Updated string `json:"updated"`
  104. Created time.Time `json:"created"`
  105. Metadata VMMetadata `json:"metadata"`
  106. Description string `json:"description"`
  107. Locked bool `json:"locked"`
  108. ConfigDrive string `json:"config_drive"`
  109. TenantID string `json:"tenant_id"`
  110. UserID string `json:"user_id"`
  111. KeyName string `json:"key_name"`
  112. OSExtendedVolumesVolumesAttached []OSExtendedVolumesVolumesAttached `json:"os-extended-volumes:volumes_attached"`
  113. OSEXTSTSTaskState string `json:"OS-EXT-STS:task_state"`
  114. OSEXTSTSPowerState int64 `json:"OS-EXT-STS:power_state"`
  115. OSEXTSTSVMState string `json:"OS-EXT-STS:vm_state"`
  116. OSEXTSRVATTRHost string `json:"OS-EXT-SRV-ATTR:host"`
  117. OSEXTSRVATTRInstanceName string `json:"OS-EXT-SRV-ATTR:instance_name"`
  118. OSEXTSRVATTRHypervisorHostname string `json:"OS-EXT-SRV-ATTR:hypervisor_hostname"`
  119. OSDCFDiskConfig string `json:"OS-DCF:diskConfig"`
  120. OSEXTAZAvailabilityZone string `json:"OS-EXT-AZ:availability_zone"`
  121. OSSchedulerHints OSSchedulerHints `json:"os:scheduler_hints"`
  122. OSEXTSRVATTRRootDeviceName string `json:"OS-EXT-SRV-ATTR:root_device_name"`
  123. OSEXTSRVATTRRamdiskID string `json:"OS-EXT-SRV-ATTR:ramdisk_id"`
  124. EnterpriseProjectID string `json:"enterprise_project_id"`
  125. OSEXTSRVATTRUserData string `json:"OS-EXT-SRV-ATTR:user_data"`
  126. OSSRVUSGLaunchedAt time.Time `json:"OS-SRV-USG:launched_at"`
  127. OSEXTSRVATTRKernelID string `json:"OS-EXT-SRV-ATTR:kernel_id"`
  128. OSEXTSRVATTRLaunchIndex int64 `json:"OS-EXT-SRV-ATTR:launch_index"`
  129. HostStatus string `json:"host_status"`
  130. OSEXTSRVATTRReservationID string `json:"OS-EXT-SRV-ATTR:reservation_id"`
  131. OSEXTSRVATTRHostname string `json:"OS-EXT-SRV-ATTR:hostname"`
  132. OSSRVUSGTerminatedAt time.Time `json:"OS-SRV-USG:terminated_at"`
  133. SysTags []SysTag `json:"sys_tags"`
  134. SecurityGroups []SecurityGroup `json:"security_groups"`
  135. EnterpriseProjectId string
  136. }
  137. func compareSet(currentSet []string, newSet []string) (add []string, remove []string, keep []string) {
  138. sort.Strings(currentSet)
  139. sort.Strings(newSet)
  140. i, j := 0, 0
  141. for i < len(currentSet) || j < len(newSet) {
  142. if i < len(currentSet) && j < len(newSet) {
  143. if currentSet[i] == newSet[j] {
  144. keep = append(keep, currentSet[i])
  145. i += 1
  146. j += 1
  147. } else if currentSet[i] < newSet[j] {
  148. remove = append(remove, currentSet[i])
  149. i += 1
  150. } else {
  151. add = append(add, newSet[j])
  152. j += 1
  153. }
  154. } else if i >= len(currentSet) {
  155. add = append(add, newSet[j])
  156. j += 1
  157. } else if j >= len(newSet) {
  158. remove = append(remove, currentSet[i])
  159. i += 1
  160. }
  161. }
  162. return add, remove, keep
  163. }
  164. // 启动盘 != 系统盘(必须是启动盘且挂载在root device上)
  165. func isBootDisk(server *SInstance, disk *SDisk) bool {
  166. if disk.GetDiskType() != api.DISK_TYPE_SYS {
  167. return false
  168. }
  169. for _, attachment := range disk.Attachments {
  170. if attachment.ServerID == server.GetId() && attachment.Device == server.OSEXTSRVATTRRootDeviceName {
  171. return true
  172. }
  173. }
  174. return false
  175. }
  176. func (self *SInstance) GetId() string {
  177. return self.ID
  178. }
  179. func (self *SInstance) GetName() string {
  180. return self.Name
  181. }
  182. func (self *SInstance) GetHostname() string {
  183. return self.OSEXTSRVATTRHostname
  184. }
  185. func (self *SInstance) GetGlobalId() string {
  186. return self.ID
  187. }
  188. func (self *SInstance) GetStatus() string {
  189. switch self.Status {
  190. case "ACTIVE":
  191. return api.VM_RUNNING
  192. case "MIGRATING", "REBUILD", "BUILD", "RESIZE", "VERIFY_RESIZE": // todo: pending ?
  193. return api.VM_STARTING
  194. case "REBOOT", "HARD_REBOOT":
  195. return api.VM_STOPPING
  196. case "SHUTOFF":
  197. return api.VM_READY
  198. default:
  199. return api.VM_UNKNOWN
  200. }
  201. }
  202. func (ins *SInstance) GetPowerStates() string {
  203. switch ins.OSEXTSTSPowerState {
  204. case 1:
  205. return api.VM_POWER_STATES_ON
  206. default:
  207. return api.VM_POWER_STATES_OFF
  208. }
  209. }
  210. func (self *SInstance) Refresh() error {
  211. new, err := self.host.zone.region.GetInstanceByID(self.GetId())
  212. new.host = self.host
  213. if err != nil {
  214. return err
  215. }
  216. if new.Status == InstanceStatusTerminated {
  217. log.Debugf("Instance already terminated.")
  218. return cloudprovider.ErrNotFound
  219. }
  220. err = jsonutils.Update(self, new)
  221. if err != nil {
  222. return err
  223. }
  224. return nil
  225. }
  226. func (self *SInstance) IsEmulated() bool {
  227. return false
  228. }
  229. func (self *SInstance) GetInstanceType() string {
  230. return self.Flavor.ID
  231. }
  232. func (self *SInstance) GetSecurityGroupIds() ([]string, error) {
  233. return self.host.zone.region.GetInstanceSecrityGroupIds(self.GetId())
  234. }
  235. // https://support.huaweicloud.com/api-ecs/ecs_02_1002.html
  236. // key 相同时value不会替换
  237. func (self *SRegion) CreateServerTags(instanceId string, tags map[string]string) error {
  238. params := map[string]interface{}{
  239. "action": "create",
  240. }
  241. tagsObj := []map[string]string{}
  242. for k, v := range tags {
  243. tagsObj = append(tagsObj, map[string]string{"key": k, "value": v})
  244. }
  245. params["tags"] = tagsObj
  246. _, err := self.ecsClient.Servers.PerformAction2("tags/action", instanceId, jsonutils.Marshal(params), "")
  247. return err
  248. }
  249. // https://support.huaweicloud.com/api-ecs/ecs_02_1003.html
  250. func (self *SRegion) DeleteServerTags(instanceId string, tagsKey []string) error {
  251. params := map[string]interface{}{
  252. "action": "delete",
  253. }
  254. tagsObj := []map[string]string{}
  255. for _, k := range tagsKey {
  256. tagsObj = append(tagsObj, map[string]string{"key": k})
  257. }
  258. params["tags"] = tagsObj
  259. _, err := self.ecsClient.Servers.PerformAction2("tags/action", instanceId, jsonutils.Marshal(params), "")
  260. return err
  261. }
  262. func (self *SInstance) SetTags(tags map[string]string, replace bool) error {
  263. existedTags, err := self.GetTags()
  264. if err != nil {
  265. return errors.Wrap(err, "self.GetTags()")
  266. }
  267. deleteTagsKey := []string{}
  268. for k := range existedTags {
  269. if replace {
  270. deleteTagsKey = append(deleteTagsKey, k)
  271. } else {
  272. if _, ok := tags[k]; ok {
  273. deleteTagsKey = append(deleteTagsKey, k)
  274. }
  275. }
  276. }
  277. if len(deleteTagsKey) > 0 {
  278. err := self.host.zone.region.DeleteServerTags(self.GetId(), deleteTagsKey)
  279. if err != nil {
  280. return errors.Wrapf(err, "self.host.zone.region.DeleteServerTags(%s,%s)", self.GetId(), deleteTagsKey)
  281. }
  282. }
  283. if len(tags) > 0 {
  284. err := self.host.zone.region.CreateServerTags(self.GetId(), tags)
  285. if err != nil {
  286. return errors.Wrapf(err, "self.host.zone.region.CreateServerTags(%s,%s)", self.GetId(), jsonutils.Marshal(tags).String())
  287. }
  288. }
  289. return nil
  290. }
  291. func (self *SInstance) GetBillingType() string {
  292. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148849.html
  293. // charging_mode “0”:按需计费 “1”:按包年包月计费
  294. if self.Metadata.ChargingMode == "1" {
  295. return billing_api.BILLING_TYPE_PREPAID
  296. } else {
  297. return billing_api.BILLING_TYPE_POSTPAID
  298. }
  299. }
  300. func (self *SInstance) GetCreatedAt() time.Time {
  301. return self.Created
  302. }
  303. func (self *SInstance) GetDescription() string {
  304. return self.Description
  305. }
  306. // charging_mode “0”:按需计费 “1”:按包年包月计费
  307. func (self *SInstance) GetExpiredAt() time.Time {
  308. var expiredTime time.Time
  309. return expiredTime
  310. }
  311. func (self *SInstance) GetIHost() cloudprovider.ICloudHost {
  312. return self.host
  313. }
  314. func (self *SInstance) GetIHostId() string {
  315. return self.host.GetGlobalId()
  316. }
  317. func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
  318. err := self.Refresh()
  319. if err != nil {
  320. return nil, err
  321. }
  322. attached := self.OSExtendedVolumesVolumesAttached
  323. disks := make([]SDisk, 0)
  324. for _, vol := range attached {
  325. disk, err := self.host.zone.region.GetDisk(vol.ID)
  326. if err != nil {
  327. return nil, err
  328. }
  329. disks = append(disks, *disk)
  330. }
  331. idisks := make([]cloudprovider.ICloudDisk, len(disks))
  332. for i := 0; i < len(disks); i += 1 {
  333. storage, err := self.host.zone.getStorageByCategory(disks[i].VolumeType)
  334. if err != nil {
  335. return nil, err
  336. }
  337. disks[i].storage = storage
  338. idisks[i] = &disks[i]
  339. // 将系统盘放到第0个位置
  340. if isBootDisk(self, &disks[i]) {
  341. _temp := idisks[0]
  342. idisks[0] = &disks[i]
  343. idisks[i] = _temp
  344. }
  345. }
  346. return idisks, nil
  347. }
  348. func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
  349. nics := make([]cloudprovider.ICloudNic, 0)
  350. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148849.html
  351. // OS-EXT-IPS.type
  352. // todo: 这里没有区分是IPv4 还是 IPv6。统一当IPv4处理了.可能会引发错误
  353. for _, ipAddresses := range self.Addresses {
  354. for _, ipAddress := range ipAddresses {
  355. if ipAddress.OSEXTIPSType == "fixed" {
  356. nic := SInstanceNic{
  357. instance: self,
  358. ipAddr: ipAddress.Addr,
  359. macAddr: ipAddress.OSEXTIPSMACMACAddr,
  360. }
  361. nics = append(nics, &nic)
  362. }
  363. }
  364. }
  365. return nics, nil
  366. }
  367. func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
  368. ips := make([]string, 0)
  369. for _, addresses := range self.Addresses {
  370. for _, address := range addresses {
  371. if address.OSEXTIPSType != "fixed" && !strings.HasPrefix(address.Addr, "100.") {
  372. ips = append(ips, address.Addr)
  373. }
  374. }
  375. }
  376. if len(ips) == 0 {
  377. return nil, nil
  378. }
  379. eips, err := self.host.zone.region.GetEips()
  380. if err != nil {
  381. return nil, err
  382. }
  383. for _, eip := range eips {
  384. if eip.PublicIPAddress == ips[0] {
  385. return &eip, nil
  386. }
  387. }
  388. return nil, nil
  389. }
  390. func (self *SInstance) GetVcpuCount() int {
  391. cpu, _ := strconv.Atoi(self.Flavor.Vcpus)
  392. return cpu
  393. }
  394. func (self *SInstance) GetVmemSizeMB() int {
  395. mem, _ := strconv.Atoi(self.Flavor.RAM)
  396. return int(mem)
  397. }
  398. func (self *SInstance) GetBootOrder() string {
  399. return "dcn"
  400. }
  401. func (self *SInstance) GetVga() string {
  402. return "std"
  403. }
  404. func (self *SInstance) GetVdi() string {
  405. return "vnc"
  406. }
  407. func (self *SInstance) GetOsArch() string {
  408. if flavor, err := self.host.zone.region.GetICloudSku(self.Flavor.ID); err == nil {
  409. return flavor.GetCpuArch()
  410. } else {
  411. log.Debugf("GetOSArch.GetICloudSku %s: %s", self.Flavor.ID, err)
  412. }
  413. t := self.GetInstanceType()
  414. if len(t) > 0 {
  415. if strings.HasPrefix(t, "k") {
  416. return apis.OS_ARCH_AARCH64
  417. }
  418. }
  419. return apis.OS_ARCH_X86
  420. }
  421. func (self *SInstance) GetOsType() cloudprovider.TOsType {
  422. return cloudprovider.TOsType(osprofile.NormalizeOSType(self.Metadata.OSType))
  423. }
  424. func (self *SInstance) GetFullOsName() string {
  425. return self.Metadata.ImageName
  426. }
  427. func (i *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo {
  428. if i.osInfo == nil {
  429. osInfo := imagetools.NormalizeImageInfo(i.Metadata.ImageName, "", i.Metadata.OSType, "", "")
  430. i.osInfo = &osInfo
  431. }
  432. return i.osInfo
  433. }
  434. func (i *SInstance) GetBios() cloudprovider.TBiosType {
  435. return cloudprovider.ToBiosType(i.getNormalizedOsInfo().OsBios)
  436. }
  437. func (i *SInstance) GetOsDist() string {
  438. return i.getNormalizedOsInfo().OsDistro
  439. }
  440. func (i *SInstance) GetOsVersion() string {
  441. return i.getNormalizedOsInfo().OsVersion
  442. }
  443. func (i *SInstance) GetOsLang() string {
  444. return i.getNormalizedOsInfo().OsLang
  445. }
  446. func (self *SInstance) GetMachine() string {
  447. return "pc"
  448. }
  449. func (self *SInstance) SetSecurityGroups(secgroupIds []string) error {
  450. currentSecgroups, err := self.host.zone.region.GetInstanceSecrityGroupIds(self.GetId())
  451. if err != nil {
  452. return err
  453. }
  454. add, remove, _ := compareSet(currentSecgroups, secgroupIds)
  455. for _, id := range add {
  456. err = self.host.zone.region.assignSecurityGroup(self.GetId(), id)
  457. if err != nil {
  458. return err
  459. }
  460. }
  461. for _, id := range remove {
  462. err := self.host.zone.region.unassignSecurityGroups(self.GetId(), id)
  463. if err != nil {
  464. return err
  465. }
  466. }
  467. return nil
  468. }
  469. func (self *SInstance) GetHypervisor() string {
  470. return api.HYPERVISOR_HCSO
  471. }
  472. func (self *SInstance) StartVM(ctx context.Context) error {
  473. if self.Status == InstanceStatusRunning {
  474. return nil
  475. }
  476. timeout := 300 * time.Second
  477. interval := 15 * time.Second
  478. startTime := time.Now()
  479. for time.Now().Sub(startTime) < timeout {
  480. err := self.Refresh()
  481. if err != nil {
  482. return err
  483. }
  484. if self.GetStatus() == api.VM_RUNNING {
  485. return nil
  486. } else if self.GetStatus() == api.VM_READY {
  487. err := self.host.zone.region.StartVM(self.GetId())
  488. if err != nil {
  489. return err
  490. }
  491. }
  492. time.Sleep(interval)
  493. }
  494. return cloudprovider.ErrTimeout
  495. }
  496. func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
  497. if self.Status == InstanceStatusStopped {
  498. return nil
  499. }
  500. if self.Status == InstanceStatusTerminated {
  501. log.Debugf("Instance already terminated.")
  502. return nil
  503. }
  504. err := self.host.zone.region.StopVM(self.GetId(), opts.IsForce)
  505. if err != nil {
  506. return err
  507. }
  508. return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 300*time.Second) // 5mintues
  509. }
  510. func (self *SInstance) DeleteVM(ctx context.Context) error {
  511. if self.Status == InstanceStatusTerminated {
  512. return nil
  513. }
  514. for {
  515. err := self.host.zone.region.DeleteVM(self.GetId())
  516. if err != nil && self.Status != InstanceStatusTerminated {
  517. log.Errorf("DeleteVM fail: %s", err)
  518. return err
  519. } else {
  520. break
  521. }
  522. }
  523. return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes
  524. }
  525. func (self *SInstance) UpdateVM(ctx context.Context, input cloudprovider.SInstanceUpdateOptions) error {
  526. return self.host.zone.region.UpdateVM(self.GetId(), input)
  527. }
  528. // https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0032380449.html
  529. // 创建云服务器过程中注入用户数据。支持注入文本、文本文件或gzip文件。
  530. // 注入内容,需要进行base64格式编码。注入内容(编码之前的内容)最大长度32KB。
  531. // 对于Linux弹性云服务器,adminPass参数传入时,user_data参数不生效。
  532. func (self *SInstance) UpdateUserData(userData string) error {
  533. return cloudprovider.ErrNotSupported
  534. }
  535. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876349.html 使用原镜像重装
  536. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876971.html 更换系统盘操作系统
  537. // 不支持调整系统盘大小
  538. func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
  539. var err error
  540. var jobId string
  541. publicKeyName := ""
  542. if len(desc.PublicKey) > 0 {
  543. publicKeyName, err = self.host.zone.region.syncKeypair(desc.PublicKey)
  544. if err != nil {
  545. return "", err
  546. }
  547. }
  548. image, err := self.host.zone.region.GetImage(desc.ImageId)
  549. if err != nil {
  550. return "", errors.Wrap(err, "SInstance.RebuildRoot.GetImage")
  551. }
  552. // Password存在的情况下,windows 系统直接使用密码
  553. if strings.ToLower(image.Platform) == strings.ToLower(osprofile.OS_TYPE_WINDOWS) && len(desc.Password) > 0 {
  554. publicKeyName = ""
  555. }
  556. userData, err := updateUserData(self.OSEXTSRVATTRUserData, image.OSVersion, desc.Account, desc.Password, desc.PublicKey)
  557. if err != nil {
  558. return "", errors.Wrap(err, "SInstance.RebuildRoot.updateUserData")
  559. }
  560. if self.Metadata.MeteringImageID == desc.ImageId {
  561. jobId, err = self.host.zone.region.RebuildRoot(ctx, self.UserID, self.GetId(), desc.Password, publicKeyName, userData)
  562. if err != nil {
  563. return "", err
  564. }
  565. } else {
  566. jobId, err = self.host.zone.region.ChangeRoot(ctx, self.UserID, self.GetId(), desc.ImageId, desc.Password, publicKeyName, userData)
  567. if err != nil {
  568. return "", err
  569. }
  570. }
  571. err = self.host.zone.region.waitTaskStatus(self.host.zone.region.ecsClient.Servers.ServiceType(), jobId, TASK_SUCCESS, 15*time.Second, 900*time.Second)
  572. if err != nil {
  573. log.Errorf("RebuildRoot task error %s", err)
  574. return "", err
  575. }
  576. err = self.Refresh()
  577. if err != nil {
  578. return "", err
  579. }
  580. idisks, err := self.GetIDisks()
  581. if err != nil {
  582. return "", err
  583. }
  584. if len(idisks) == 0 {
  585. return "", fmt.Errorf("server %s has no volume attached.", self.GetId())
  586. }
  587. return idisks[0].GetId(), nil
  588. }
  589. func (self *SInstance) DeployVM(ctx context.Context, opts *cloudprovider.SInstanceDeployOptions) error {
  590. return self.host.zone.region.DeployVM(self.GetId(), opts)
  591. }
  592. func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
  593. instanceTypes := []string{}
  594. if len(config.InstanceType) > 0 {
  595. instanceTypes = []string{config.InstanceType}
  596. } else {
  597. flavors, err := self.host.zone.region.GetMatchInstanceTypes(config.Cpu, config.MemoryMB, self.OSEXTAZAvailabilityZone)
  598. if err != nil {
  599. return errors.Wrapf(err, "GetMatchInstanceTypes")
  600. }
  601. for _, flavor := range flavors {
  602. instanceTypes = append(instanceTypes, flavor.ID)
  603. }
  604. }
  605. var err error
  606. for _, instanceType := range instanceTypes {
  607. err = self.host.zone.region.ChangeVMConfig(self.GetId(), instanceType)
  608. if err != nil {
  609. log.Warningf("ChangeVMConfig %s for %s error: %v", self.GetId(), instanceType, err)
  610. } else {
  611. return cloudprovider.WaitStatusWithDelay(self, api.VM_READY, 15*time.Second, 15*time.Second, 180*time.Second)
  612. }
  613. }
  614. if err != nil {
  615. return errors.Wrapf(err, "ChangeVMConfig")
  616. }
  617. return fmt.Errorf("Failed to change vm config, specification not supported")
  618. }
  619. // todo:// 返回jsonobject感觉很诡异。不能直接知道内部细节
  620. func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
  621. return self.host.zone.region.GetInstanceVNCUrl(self.GetId())
  622. }
  623. func (self *SInstance) NextDeviceName() (string, error) {
  624. prefix := "s"
  625. if strings.Contains(self.OSEXTSRVATTRRootDeviceName, "/vd") {
  626. prefix = "v"
  627. }
  628. currents := []string{}
  629. for _, item := range self.OSExtendedVolumesVolumesAttached {
  630. currents = append(currents, strings.ToLower(item.Device))
  631. }
  632. for i := 0; i < 25; i++ {
  633. device := fmt.Sprintf("/dev/%sd%s", prefix, string([]byte{byte(98 + i)}))
  634. if ok, _ := utils.InStringArray(device, currents); !ok {
  635. return device, nil
  636. }
  637. }
  638. return "", fmt.Errorf("disk devicename out of index, current deivces: %s", currents)
  639. }
  640. func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
  641. device, err := self.NextDeviceName()
  642. if err != nil {
  643. return errors.Wrap(err, "Instance.AttachDisk.NextDeviceName")
  644. }
  645. err = self.host.zone.region.AttachDisk(self.GetId(), diskId, device)
  646. if err != nil {
  647. return errors.Wrap(err, "Instance.AttachDisk.AttachDisk")
  648. }
  649. return cloudprovider.Wait(5*time.Second, 60*time.Second, func() (bool, error) {
  650. disk, err := self.host.zone.region.GetDisk(diskId)
  651. if err != nil {
  652. log.Debugf("Instance.AttachDisk.GetDisk %s", err)
  653. return false, nil
  654. }
  655. if disk.Status == "in-use" {
  656. return true, nil
  657. }
  658. return false, nil
  659. })
  660. }
  661. func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
  662. err := self.host.zone.region.DetachDisk(self.GetId(), diskId)
  663. if err != nil {
  664. return errors.Wrap(err, "Instance.DetachDisk")
  665. }
  666. return cloudprovider.Wait(5*time.Second, 60*time.Second, func() (bool, error) {
  667. disk, err := self.host.zone.region.GetDisk(diskId)
  668. if err != nil {
  669. log.Debugf("Instance.DetachDisk.GetDisk %s", err)
  670. return false, nil
  671. }
  672. if disk.Status == "available" {
  673. return true, nil
  674. }
  675. return false, nil
  676. })
  677. }
  678. func (self *SInstance) Renew(bc billing.SBillingCycle) error {
  679. return cloudprovider.ErrNotSupported
  680. }
  681. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148850.html
  682. func (self *SRegion) GetInstances() ([]SInstance, error) {
  683. queries := make(map[string]string)
  684. if len(self.client.projectId) > 0 {
  685. queries["project_id"] = self.client.projectId
  686. }
  687. instances := make([]SInstance, 0)
  688. err := doListAllWithPagerOffset(self.ecsClient.Servers.List, queries, &instances)
  689. return instances, err
  690. }
  691. func (self *SRegion) GetInstanceByID(instanceId string) (SInstance, error) {
  692. instance := SInstance{}
  693. err := DoGet(self.ecsClient.Servers.Get, instanceId, nil, &instance)
  694. if instance.Status == "DELETED" {
  695. return instance, errors.Wrap(cloudprovider.ErrNotFound, "GetInstanceByID")
  696. }
  697. return instance, err
  698. }
  699. func (self *SRegion) GetInstanceByIds(ids []string) ([]SInstance, int, error) {
  700. instances := make([]SInstance, 0)
  701. for _, instanceId := range ids {
  702. instance, err := self.GetInstanceByID(instanceId)
  703. if err != nil {
  704. return nil, 0, err
  705. }
  706. instances = append(instances, instance)
  707. }
  708. return instances, len(instances), nil
  709. }
  710. /*
  711. 系统盘大小取值范围:1-1024 GB,且必须不小于镜像min_disk.
  712. */
  713. type SServerCreate struct {
  714. AvailabilityZone string `json:"availability_zone"`
  715. Name string `json:"name"`
  716. ImageRef string `json:"imageRef"`
  717. RootVolume RootVolume `json:"root_volume"`
  718. DataVolumes []DataVolume `json:"data_volumes"`
  719. FlavorRef string `json:"flavorRef"`
  720. UserData string `json:"user_data"`
  721. Vpcid string `json:"vpcid"`
  722. SecurityGroups []SecGroup `json:"security_groups"`
  723. Nics []NIC `json:"nics"`
  724. KeyName string `json:"key_name"`
  725. AdminPass string `json:"adminPass"`
  726. Count int64 `json:"count"`
  727. Extendparam ServerExtendparam `json:"extendparam"`
  728. ServerTags []ServerTag `json:"server_tags"`
  729. Description string `json:"description"`
  730. }
  731. type DataVolume struct {
  732. Volumetype string `json:"volumetype"`
  733. SizeGB int `json:"size"`
  734. Extendparam *DataVolumeExtendparam `json:"extendparam,omitempty"`
  735. Multiattach *bool `json:"multiattach,omitempty"`
  736. HwPassthrough *string `json:"hw:passthrough,omitempty"`
  737. }
  738. type DataVolumeExtendparam struct {
  739. SnapshotID string `json:"snapshotId"`
  740. }
  741. type ServerExtendparam struct {
  742. ChargingMode string `json:"chargingMode"` // 计费模式 prePaid|postPaid
  743. PeriodType string `json:"periodType"` // 周期类型:month|year
  744. PeriodNum string `json:"periodNum"` // 订购周期数:periodType=month(周期类型为月)时,取值为[1,9]。periodType=year(周期类型为年)时,取值为1。
  745. IsAutoRenew string `json:"isAutoRenew"` // 是否自动续订 true|false
  746. IsAutoPay string `json:"isAutoPay"` // 是否自动从客户的账户中支付 true|false
  747. RegionID string `json:"regionID"`
  748. EnterpriseProjectId string `json:"enterprise_project_id,omitempty"`
  749. }
  750. type NIC struct {
  751. SubnetID string `json:"subnet_id"` // 网络ID. 与 SNetwork里的ID对应。统一使用这个ID
  752. IpAddress string `json:"ip_address"`
  753. }
  754. type RootVolume struct {
  755. Volumetype string `json:"volumetype"`
  756. SizeGB int `json:"size"`
  757. }
  758. type SecGroup struct {
  759. ID string `json:"id"`
  760. }
  761. type ServerTag struct {
  762. Key string `json:"key"`
  763. Value string `json:"value"`
  764. }
  765. /*
  766. 包月机器退订规则: https://support.huaweicloud.com/usermanual-billing/zh-cn_topic_0083138805.html
  767. 5天无理由全额退订:新购资源(不包含续费资源)在开通的五天内且退订次数不超过10次(每账号每年10次)的符合5天无理由全额退订。
  768. 非5天无理由退订:不符合5天无理由全额退订条件的退订,都属于非5天无理由退订。非5天无理由退订,不限制退订次数,但需要收取退订手续费。
  769. 退订资源的方法: https://support.huaweicloud.com/usermanual-billing/zh-cn_topic_0072297197.html
  770. */
  771. func (self *SRegion) CreateInstance(name string, imageId string, instanceType string, SubnetId string,
  772. securityGroupIds []string, vpcId string, zoneId string, desc string, disks []SDisk, ipAddr string,
  773. keypair string, publicKey string, passwd string, userData string, bc *billing.SBillingCycle, projectId string, tags map[string]string) (string, error) {
  774. params := SServerCreate{}
  775. params.AvailabilityZone = zoneId
  776. params.Name = name
  777. params.FlavorRef = instanceType
  778. params.ImageRef = imageId
  779. params.Description = desc
  780. params.Count = 1
  781. params.Nics = []NIC{{SubnetID: SubnetId, IpAddress: ipAddr}}
  782. groups := []SecGroup{}
  783. for _, id := range securityGroupIds {
  784. groups = append(groups, SecGroup{ID: id})
  785. }
  786. params.SecurityGroups = groups
  787. params.Vpcid = vpcId
  788. for i, disk := range disks {
  789. if i == 0 {
  790. params.RootVolume.Volumetype = disk.VolumeType
  791. params.RootVolume.SizeGB = disk.SizeGB
  792. } else {
  793. dataVolume := DataVolume{}
  794. dataVolume.Volumetype = disk.VolumeType
  795. dataVolume.SizeGB = disk.SizeGB
  796. params.DataVolumes = append(params.DataVolumes, dataVolume)
  797. }
  798. }
  799. if len(projectId) > 0 {
  800. params.Extendparam.EnterpriseProjectId = projectId
  801. }
  802. // billing type
  803. if bc != nil {
  804. params.Extendparam.ChargingMode = PRE_PAID
  805. if bc.GetMonths() <= 9 {
  806. params.Extendparam.PeriodNum = strconv.Itoa(bc.GetMonths())
  807. params.Extendparam.PeriodType = "month"
  808. } else {
  809. params.Extendparam.PeriodNum = strconv.Itoa(bc.GetYears())
  810. params.Extendparam.PeriodType = "year"
  811. }
  812. params.Extendparam.RegionID = self.GetId()
  813. if bc.AutoRenew {
  814. params.Extendparam.IsAutoRenew = "true"
  815. } else {
  816. params.Extendparam.IsAutoRenew = "false"
  817. }
  818. params.Extendparam.IsAutoPay = "true"
  819. } else {
  820. params.Extendparam.ChargingMode = POST_PAID
  821. }
  822. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212668.html#ZH-CN_TOPIC_0020212668__table761103195216
  823. if len(keypair) > 0 {
  824. params.KeyName = keypair
  825. } else {
  826. params.AdminPass = passwd
  827. }
  828. if len(userData) > 0 {
  829. params.UserData = userData
  830. }
  831. if len(tags) > 0 {
  832. serverTags := []ServerTag{}
  833. for k, v := range tags {
  834. serverTags = append(serverTags, ServerTag{Key: k, Value: v})
  835. }
  836. params.ServerTags = serverTags
  837. }
  838. serverObj := jsonutils.Marshal(params)
  839. createParams := jsonutils.NewDict()
  840. createParams.Add(serverObj, "server")
  841. _id, err := self.ecsClient.Servers.AsyncCreate(createParams)
  842. if err != nil {
  843. return "", err
  844. }
  845. var ids []string
  846. if params.Extendparam.ChargingMode == POST_PAID {
  847. // 按需计费
  848. ids, err = self.GetAllSubTaskEntityIDs(self.ecsClient.Servers.ServiceType(), _id, "server_id")
  849. } else {
  850. // 包年包月
  851. return "", errors.Wrap(cloudprovider.ErrNotSupported, "CreateInstance")
  852. }
  853. if err != nil {
  854. return "", err
  855. } else if len(ids) == 0 {
  856. return "", fmt.Errorf("CreateInstance job %s result is emtpy", _id)
  857. } else if len(ids) == 1 {
  858. return ids[0], nil
  859. } else {
  860. return "", fmt.Errorf("CreateInstance job %s mutliple instance id returned. %s", _id, ids)
  861. }
  862. }
  863. func (self *SRegion) assignSecurityGroup(instanceId, secgroupId string) error {
  864. _, err := self.post(SERVICE_ECS, fmt.Sprintf("servers/%s/action", instanceId), map[string]interface{}{
  865. "addSecurityGroup": map[string]interface{}{
  866. "name": secgroupId,
  867. },
  868. })
  869. return err
  870. }
  871. func (self *SRegion) unassignSecurityGroups(instanceId, secgroupId string) error {
  872. _, err := self.post(SERVICE_ECS, fmt.Sprintf("servers/%s/action", instanceId), map[string]interface{}{
  873. "removeSecurityGroup": map[string]interface{}{
  874. "name": secgroupId,
  875. },
  876. })
  877. return err
  878. }
  879. func (self *SRegion) GetInstanceStatus(instanceId string) (string, error) {
  880. instance, err := self.GetInstanceByID(instanceId)
  881. if err != nil {
  882. return "", err
  883. }
  884. return instance.Status, nil
  885. }
  886. func (self *SRegion) instanceStatusChecking(instanceId, status string) error {
  887. remoteStatus, err := self.GetInstanceStatus(instanceId)
  888. if err != nil {
  889. log.Errorf("Fail to get instance status: %s", err)
  890. return err
  891. }
  892. if status != remoteStatus {
  893. log.Errorf("instanceStatusChecking: vm status is %s expect %s", remoteStatus, status)
  894. return cloudprovider.ErrInvalidStatus
  895. }
  896. return nil
  897. }
  898. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212207.html
  899. func (self *SRegion) StartVM(instanceId string) error {
  900. rstatus, err := self.GetInstanceStatus(instanceId)
  901. if err != nil {
  902. return err
  903. }
  904. if rstatus == InstanceStatusRunning {
  905. return nil
  906. }
  907. if rstatus != InstanceStatusStopped {
  908. log.Errorf("instanceStatusChecking: vm status is %s expect %s", rstatus, InstanceStatusStopped)
  909. return cloudprovider.ErrInvalidStatus
  910. }
  911. params := jsonutils.NewDict()
  912. startObj := jsonutils.NewDict()
  913. serversObj := jsonutils.NewArray()
  914. serverObj := jsonutils.NewDict()
  915. serverObj.Add(jsonutils.NewString(instanceId), "id")
  916. serversObj.Add(serverObj)
  917. startObj.Add(serversObj, "servers")
  918. params.Add(startObj, "os-start")
  919. _, err = self.ecsClient.Servers.PerformAction2("action", "", params, "")
  920. return err
  921. }
  922. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212651.html
  923. func (self *SRegion) StopVM(instanceId string, isForce bool) error {
  924. rstatus, err := self.GetInstanceStatus(instanceId)
  925. if err != nil {
  926. return err
  927. }
  928. if rstatus == InstanceStatusStopped {
  929. return nil
  930. }
  931. if rstatus != InstanceStatusRunning {
  932. log.Errorf("instanceStatusChecking: vm status is %s expect %s", rstatus, InstanceStatusRunning)
  933. return cloudprovider.ErrInvalidStatus
  934. }
  935. params := jsonutils.NewDict()
  936. stopObj := jsonutils.NewDict()
  937. serversObj := jsonutils.NewArray()
  938. serverObj := jsonutils.NewDict()
  939. serverObj.Add(jsonutils.NewString(instanceId), "id")
  940. serversObj.Add(serverObj)
  941. stopObj.Add(serversObj, "servers")
  942. if isForce {
  943. stopObj.Add(jsonutils.NewString("HARD"), "type")
  944. } else {
  945. stopObj.Add(jsonutils.NewString("SOFT"), "type")
  946. }
  947. params.Add(stopObj, "os-stop")
  948. _, err = self.ecsClient.Servers.PerformAction2("action", "", params, "")
  949. return err
  950. }
  951. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212679.html
  952. // 只删除主机。弹性IP和数据盘需要单独删除
  953. func (self *SRegion) DeleteVM(instanceId string) error {
  954. remoteStatus, err := self.GetInstanceStatus(instanceId)
  955. if err != nil {
  956. return err
  957. }
  958. if remoteStatus != InstanceStatusStopped {
  959. log.Errorf("DeleteVM vm status is %s expect %s", remoteStatus, InstanceStatusStopped)
  960. return cloudprovider.ErrInvalidStatus
  961. }
  962. params := jsonutils.NewDict()
  963. serversObj := jsonutils.NewArray()
  964. serverObj := jsonutils.NewDict()
  965. serverObj.Add(jsonutils.NewString(instanceId), "id")
  966. serversObj.Add(serverObj)
  967. params.Add(serversObj, "servers")
  968. params.Add(jsonutils.NewBool(false), "delete_publicip")
  969. params.Add(jsonutils.NewBool(false), "delete_volume")
  970. _, err = self.ecsClient.Servers.PerformAction2("delete", "", params, "")
  971. return err
  972. }
  973. func (self *SRegion) UpdateVM(instanceId string, input cloudprovider.SInstanceUpdateOptions) error {
  974. params := jsonutils.NewDict()
  975. serverObj := jsonutils.NewDict()
  976. serverObj.Add(jsonutils.NewString(input.NAME), "name")
  977. serverObj.Add(jsonutils.NewString(input.Description), "description")
  978. params.Add(serverObj, "server")
  979. _, err := self.ecsClient.Servers.Update(instanceId, params)
  980. return err
  981. }
  982. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876349.html
  983. // 返回job id
  984. func (self *SRegion) RebuildRoot(ctx context.Context, userId, instanceId, passwd, publicKeyName, userData string) (string, error) {
  985. params := jsonutils.NewDict()
  986. reinstallObj := jsonutils.NewDict()
  987. if len(publicKeyName) > 0 {
  988. reinstallObj.Add(jsonutils.NewString(publicKeyName), "keyname")
  989. } else if len(passwd) > 0 {
  990. reinstallObj.Add(jsonutils.NewString(passwd), "adminpass")
  991. } else {
  992. return "", fmt.Errorf("both password and publicKey are empty.")
  993. }
  994. if len(userData) > 0 {
  995. meta := jsonutils.NewDict()
  996. meta.Add(jsonutils.NewString(userData), "user_data")
  997. reinstallObj.Add(meta, "metadata")
  998. }
  999. if len(userId) > 0 {
  1000. reinstallObj.Add(jsonutils.NewString(userId), "userid")
  1001. }
  1002. params.Add(reinstallObj, "os-reinstall")
  1003. ret, err := self.ecsClient.ServersV2.PerformAction2("reinstallos", instanceId, params, "")
  1004. if err != nil {
  1005. return "", err
  1006. }
  1007. return ret.GetString("job_id")
  1008. }
  1009. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876971.html
  1010. // 返回job id
  1011. func (self *SRegion) ChangeRoot(ctx context.Context, userId, instanceId, imageId, passwd, publicKeyName, userData string) (string, error) {
  1012. params := jsonutils.NewDict()
  1013. changeOsObj := jsonutils.NewDict()
  1014. if len(publicKeyName) > 0 {
  1015. changeOsObj.Add(jsonutils.NewString(publicKeyName), "keyname")
  1016. } else if len(passwd) > 0 {
  1017. changeOsObj.Add(jsonutils.NewString(passwd), "adminpass")
  1018. } else {
  1019. return "", fmt.Errorf("both password and publicKey are empty.")
  1020. }
  1021. if len(userData) > 0 {
  1022. meta := jsonutils.NewDict()
  1023. meta.Add(jsonutils.NewString(userData), "user_data")
  1024. changeOsObj.Add(meta, "metadata")
  1025. }
  1026. if len(userId) > 0 {
  1027. changeOsObj.Add(jsonutils.NewString(userId), "userid")
  1028. }
  1029. changeOsObj.Add(jsonutils.NewString(imageId), "imageid")
  1030. params.Add(changeOsObj, "os-change")
  1031. ret, err := self.ecsClient.ServersV2.PerformAction2("changeos", instanceId, params, "")
  1032. if err != nil {
  1033. return "", err
  1034. }
  1035. return ret.GetString("job_id")
  1036. }
  1037. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212692.html
  1038. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0110109377.html
  1039. // 一键式重置密码 需要安装安装一键式重置密码插件 https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0068095385.html
  1040. // 目前不支持直接重置密钥
  1041. func (self *SRegion) DeployVM(instanceId string, opts *cloudprovider.SInstanceDeployOptions) error {
  1042. if len(opts.Password) > 0 {
  1043. params := jsonutils.NewDict()
  1044. passwdObj := jsonutils.NewDict()
  1045. passwdObj.Add(jsonutils.NewString(opts.Password), "new_password")
  1046. params.Add(passwdObj, "reset-password")
  1047. err := DoUpdateWithSpec(self.ecsClient.NovaServers.UpdateInContextWithSpec, instanceId, "os-reset-password", params)
  1048. if err != nil {
  1049. return err
  1050. }
  1051. }
  1052. return nil
  1053. }
  1054. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212653.html
  1055. func (self *SRegion) ChangeVMConfig(instanceId string, instanceType string) error {
  1056. self.ecsClient.Servers.SetVersion("v1.1")
  1057. defer self.ecsClient.Servers.SetVersion("v1")
  1058. params := jsonutils.NewDict()
  1059. resizeObj := jsonutils.NewDict()
  1060. resizeObj.Add(jsonutils.NewString(instanceType), "flavorRef")
  1061. params.Add(resizeObj, "resize")
  1062. _, err := self.ecsClient.Servers.PerformAction2("resize", instanceId, params, "")
  1063. return errors.Wrapf(err, "PerformAction2(resize)")
  1064. }
  1065. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0142763126.html 微版本2.6及以上?
  1066. // https://support.huaweicloud.com/api-ecs/ecs_02_0208.html
  1067. func (self *SRegion) GetInstanceVNCUrl(instanceId string) (*cloudprovider.ServerVncOutput, error) {
  1068. params := jsonutils.NewDict()
  1069. vncObj := jsonutils.NewDict()
  1070. vncObj.Add(jsonutils.NewString("novnc"), "type")
  1071. vncObj.Add(jsonutils.NewString("vnc"), "protocol")
  1072. params.Add(vncObj, "remote_console")
  1073. resp, err := self.ecsClient.Servers.PerformAction2("remote_console", instanceId, params, "remote_console")
  1074. if err != nil {
  1075. return nil, err
  1076. }
  1077. ret := &cloudprovider.ServerVncOutput{
  1078. Hypervisor: api.HYPERVISOR_HCSO,
  1079. }
  1080. resp.Unmarshal(ret)
  1081. ret.Protocol = "huawei"
  1082. return ret, nil
  1083. }
  1084. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0022472987.html
  1085. // XEN平台虚拟机device为必选参数。
  1086. func (self *SRegion) AttachDisk(instanceId string, diskId string, device string) error {
  1087. params := jsonutils.NewDict()
  1088. volumeObj := jsonutils.NewDict()
  1089. volumeObj.Add(jsonutils.NewString(diskId), "volumeId")
  1090. if len(device) > 0 {
  1091. volumeObj.Add(jsonutils.NewString(device), "device")
  1092. }
  1093. params.Add(volumeObj, "volumeAttachment")
  1094. _, err := self.ecsClient.Servers.PerformAction2("attachvolume", instanceId, params, "")
  1095. return err
  1096. }
  1097. // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0022472988.html
  1098. // 默认非强制卸载。delete_flag=0
  1099. func (self *SRegion) DetachDisk(instanceId string, diskId string) error {
  1100. path := fmt.Sprintf("detachvolume/%s", diskId)
  1101. err := DoDeleteWithSpec(self.ecsClient.Servers.DeleteInContextWithSpec, nil, instanceId, path, nil, nil)
  1102. //volume a2091934-2669-4fca-8eb4-a950c1836b3c is not in server 49b053d2-f798-432f-af55-76eb6ef2c769 attach volume list => 磁盘已经被卸载了
  1103. if err != nil && strings.Contains(err.Error(), fmt.Sprintf("is not in server")) && strings.Contains(err.Error(), fmt.Sprintf("attach volume list")) {
  1104. return nil
  1105. }
  1106. return err
  1107. }
  1108. func (self *SRegion) GetInstanceSecrityGroupIds(instanceId string) ([]string, error) {
  1109. resp, err := self.list(SERVICE_ECS, fmt.Sprintf("servers/%s/os-security-groups", instanceId), nil)
  1110. if err != nil {
  1111. return nil, err
  1112. }
  1113. ret := []struct {
  1114. Id string
  1115. }{}
  1116. err = resp.Unmarshal(&ret, "security_groups")
  1117. if err != nil {
  1118. return nil, err
  1119. }
  1120. ids := []string{}
  1121. for _, sec := range ret {
  1122. ids = append(ids, sec.Id)
  1123. }
  1124. return ids, nil
  1125. }
  1126. func (self *SInstance) GetProjectId() string {
  1127. return self.EnterpriseProjectId
  1128. }
  1129. func (self *SInstance) GetError() error {
  1130. return nil
  1131. }
  1132. func updateUserData(userData, osVersion, username, password, publicKey string) (string, error) {
  1133. winOS := strings.ToLower(osprofile.OS_TYPE_WINDOWS)
  1134. osVersion = strings.ToLower(osVersion)
  1135. config := &cloudinit.SCloudConfig{}
  1136. if strings.Contains(osVersion, winOS) {
  1137. if _config, err := cloudinit.ParseUserDataBase64(userData); err == nil {
  1138. config = _config
  1139. } else {
  1140. log.Debugf("updateWindowsUserData invalid userdata %s", userData)
  1141. }
  1142. } else {
  1143. if _config, err := cloudinit.ParseUserDataBase64(userData); err == nil {
  1144. config = _config
  1145. } else {
  1146. return "", fmt.Errorf("updateLinuxUserData invalid userdata %s", userData)
  1147. }
  1148. }
  1149. user := cloudinit.NewUser(username)
  1150. config.RemoveUser(user)
  1151. config.DisableRoot = 0
  1152. if len(password) > 0 {
  1153. config.SshPwauth = cloudinit.SSH_PASSWORD_AUTH_ON
  1154. user.Password(password)
  1155. config.MergeUser(user)
  1156. }
  1157. if len(publicKey) > 0 {
  1158. user.SshKey(publicKey)
  1159. config.MergeUser(user)
  1160. }
  1161. if strings.Contains(osVersion, winOS) {
  1162. userData, err := updateWindowsUserData(config.UserDataPowerShell(), osVersion, username, password)
  1163. if err != nil {
  1164. return "", errors.Wrap(err, "updateUserData.updateWindowsUserData")
  1165. }
  1166. return userData, nil
  1167. } else {
  1168. return config.UserDataBase64(), nil
  1169. }
  1170. }
  1171. func updateWindowsUserData(userData string, osVersion string, username, password string) (string, error) {
  1172. // Windows Server 2003, Windows Vista, Windows Server 2008, Windows Server 2003 R2, Windows Server 2000, Windows Server 2012, Windows Server 2003 with SP1, Windows 8
  1173. oldVersions := []string{"2000", "2003", "2008", "2012", "Vista"}
  1174. isOldVersion := false
  1175. for i := range oldVersions {
  1176. if strings.Contains(osVersion, oldVersions[i]) {
  1177. isOldVersion = true
  1178. }
  1179. }
  1180. shells := ""
  1181. if isOldVersion {
  1182. shells += fmt.Sprintf("rem cmd\n")
  1183. if username == "Administrator" {
  1184. shells += fmt.Sprintf("net user %s %s\n", username, password)
  1185. } else {
  1186. shells += fmt.Sprintf("net user %s %s /add\n", username, password)
  1187. shells += fmt.Sprintf("net localgroup administrators %s /add\n", username)
  1188. }
  1189. shells += fmt.Sprintf("net user %s /active:yes", username)
  1190. } else {
  1191. if !strings.HasPrefix(userData, "#ps1") {
  1192. shells = fmt.Sprintf("#ps1\n%s", userData)
  1193. }
  1194. }
  1195. return base64.StdEncoding.EncodeToString([]byte(shells)), nil
  1196. }
  1197. func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
  1198. params := map[string]string{
  1199. "name": opts.Name,
  1200. "instance_id": instanceId,
  1201. }
  1202. if len(opts.Notes) > 0 {
  1203. params["description"] = func() string {
  1204. opts.Notes = strings.ReplaceAll(opts.Notes, "<", "")
  1205. opts.Notes = strings.ReplaceAll(opts.Notes, ">", "")
  1206. opts.Notes = strings.ReplaceAll(opts.Notes, "\n", "")
  1207. if len(opts.Notes) > 1024 {
  1208. opts.Notes = opts.Notes[:1024]
  1209. }
  1210. return opts.Notes
  1211. }()
  1212. }
  1213. resp, err := self.ecsClient.Images.CreateInContextWithSpec(nil, "action", jsonutils.Marshal(params), "")
  1214. if err != nil {
  1215. return nil, errors.Wrapf(err, "Images.Create")
  1216. }
  1217. jobId, err := resp.GetString("job_id")
  1218. if err != nil {
  1219. return nil, errors.Wrapf(err, "resp.GetString(job_id)")
  1220. }
  1221. err = self.waitTaskStatus(self.ecsClient.Images.ServiceType(), jobId, TASK_SUCCESS, 15*time.Second, 10*time.Minute)
  1222. if err != nil {
  1223. return nil, errors.Wrapf(err, "waitTaskStatus")
  1224. }
  1225. imageId, err := self.GetTaskEntityID(self.ecsClient.Images.ServiceType(), jobId, "image_id")
  1226. if err != nil {
  1227. return nil, errors.Wrapf(err, "GetTaskEntityID")
  1228. }
  1229. image, err := self.GetImage(imageId)
  1230. if err != nil {
  1231. return nil, errors.Wrapf(err, "GetImage(%s)", imageId)
  1232. }
  1233. image.storageCache = self.getStoragecache()
  1234. return image, nil
  1235. }
  1236. func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
  1237. image, err := self.host.zone.region.SaveImage(self.ID, opts)
  1238. if err != nil {
  1239. return nil, errors.Wrapf(err, "SaveImage")
  1240. }
  1241. return image, nil
  1242. }