instance.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  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 apsara
  15. import (
  16. "context"
  17. "fmt"
  18. "sort"
  19. "strings"
  20. "time"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/errors"
  24. "yunion.io/x/pkg/util/billing"
  25. "yunion.io/x/pkg/util/imagetools"
  26. "yunion.io/x/pkg/util/osprofile"
  27. "yunion.io/x/pkg/util/seclib"
  28. "yunion.io/x/pkg/utils"
  29. api "yunion.io/x/cloudmux/pkg/apis/compute"
  30. "yunion.io/x/cloudmux/pkg/cloudprovider"
  31. "yunion.io/x/cloudmux/pkg/multicloud"
  32. )
  33. const (
  34. // Running:运行中
  35. //Starting:启动中
  36. //Stopping:停止中
  37. //Stopped:已停止
  38. InstanceStatusStopped = "Stopped"
  39. InstanceStatusRunning = "Running"
  40. InstanceStatusStopping = "Stopping"
  41. InstanceStatusStarting = "Starting"
  42. )
  43. type SDedicatedHostAttribute struct {
  44. DedicatedHostId string
  45. DedicatedHostName string
  46. }
  47. type SIpAddress struct {
  48. IpAddress []string
  49. }
  50. type SNetworkInterfaces struct {
  51. NetworkInterface []SNetworkInterface
  52. }
  53. type SOperationLocks struct {
  54. LockReason []string
  55. }
  56. type SSecurityGroupIds struct {
  57. SecurityGroupId []string
  58. }
  59. // {"NatIpAddress":"","PrivateIpAddress":{"IpAddress":["192.168.220.214"]},"VSwitchId":"vsw-2ze9cqwza4upoyujq1thd","VpcId":"vpc-2zer4jy8ix3i8f0coc5uw"}
  60. type SVpcAttributes struct {
  61. NatIpAddress string
  62. PrivateIpAddress SIpAddress
  63. VSwitchId string
  64. VpcId string
  65. }
  66. type SInstance struct {
  67. multicloud.SInstanceBase
  68. ApsaraTags
  69. host *SHost
  70. osInfo *imagetools.ImageInfo
  71. // idisks []cloudprovider.ICloudDisk
  72. AutoReleaseTime string
  73. ClusterId string
  74. Cpu int
  75. CreationTime time.Time
  76. DedicatedHostAttribute SDedicatedHostAttribute
  77. Description string
  78. DeviceAvailable bool
  79. EipAddress SEipAddress
  80. ExpiredTime time.Time
  81. GPUAmount int
  82. GPUSpec string
  83. HostName string
  84. ImageId string
  85. InnerIpAddress SIpAddress
  86. InstanceChargeType TChargeType
  87. InstanceId string
  88. InstanceName string
  89. InstanceNetworkType string
  90. InstanceType string
  91. InstanceTypeFamily string
  92. InternetChargeType TInternetChargeType
  93. InternetMaxBandwidthIn int
  94. InternetMaxBandwidthOut int
  95. IoOptimized bool
  96. KeyPairName string
  97. Memory int
  98. NetworkInterfaces SNetworkInterfaces
  99. OSName string
  100. OSType string
  101. OperationLocks SOperationLocks
  102. PublicIpAddress SIpAddress
  103. Recyclable bool
  104. RegionId string
  105. SaleCycle string
  106. SecurityGroupIds SSecurityGroupIds
  107. SerialNumber string
  108. SpotPriceLimit string
  109. SpotStrategy string
  110. StartTime time.Time
  111. Status string
  112. StoppedMode string
  113. VlanId string
  114. VpcAttributes SVpcAttributes
  115. ZoneId string
  116. DepartmentInfo
  117. }
  118. // {"AutoReleaseTime":"","ClusterId":"","Cpu":1,"CreationTime":"2018-05-23T07:58Z","DedicatedHostAttribute":{"DedicatedHostId":"","DedicatedHostName":""},"Description":"","DeviceAvailable":true,"EipAddress":{"AllocationId":"","InternetChargeType":"","IpAddress":""},"ExpiredTime":"2018-05-30T16:00Z","GPUAmount":0,"GPUSpec":"","HostName":"iZ2ze57isp1ali72tzkjowZ","ImageId":"centos_7_04_64_20G_alibase_201701015.vhd","InnerIpAddress":{"IpAddress":[]},"InstanceChargeType":"PrePaid","InstanceId":"i-2ze57isp1ali72tzkjow","InstanceName":"gaoxianqi-test-7days","InstanceNetworkType":"vpc","InstanceType":"ecs.t5-lc2m1.nano","InstanceTypeFamily":"ecs.t5","InternetChargeType":"PayByBandwidth","InternetMaxBandwidthIn":-1,"InternetMaxBandwidthOut":0,"IoOptimized":true,"Memory":512,"NetworkInterfaces":{"NetworkInterface":[{"MacAddress":"00:16:3e:10:f0:c9","NetworkInterfaceId":"eni-2zecqsagtpztl6x5hu2r","PrimaryIpAddress":"192.168.220.214"}]},"OSName":"CentOS 7.4 64位","OSType":"linux","OperationLocks":{"LockReason":[]},"PublicIpAddress":{"IpAddress":[]},"Recyclable":false,"RegionId":"cn-beijing","ResourceGroupId":"","SaleCycle":"Week","SecurityGroupIds":{"SecurityGroupId":["sg-2zecqsagtpztl6x9zynl"]},"SerialNumber":"df05d9b4-df3d-4400-88d1-5f843f0dd088","SpotPriceLimit":0.000000,"SpotStrategy":"NoSpot","StartTime":"2018-05-23T07:58Z","Status":"Running","StoppedMode":"Not-applicable","VlanId":"","VpcAttributes":{"NatIpAddress":"","PrivateIpAddress":{"IpAddress":["192.168.220.214"]},"VSwitchId":"vsw-2ze9cqwza4upoyujq1thd","VpcId":"vpc-2zer4jy8ix3i8f0coc5uw"},"ZoneId":"cn-beijing-f"}
  119. func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit int) ([]SInstance, int, error) {
  120. if limit > 50 || limit <= 0 {
  121. limit = 50
  122. }
  123. params := make(map[string]string)
  124. params["RegionId"] = self.RegionId
  125. params["PageSize"] = fmt.Sprintf("%d", limit)
  126. params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1)
  127. if len(zoneId) > 0 {
  128. params["ZoneId"] = zoneId
  129. }
  130. if ids != nil && len(ids) > 0 {
  131. params["InstanceIds"] = jsonutils.Marshal(ids).String()
  132. }
  133. body, err := self.ecsRequest("DescribeInstances", params)
  134. if err != nil {
  135. log.Errorf("GetInstances fail %s", err)
  136. return nil, 0, err
  137. }
  138. instances := make([]SInstance, 0)
  139. err = body.Unmarshal(&instances, "Instances", "Instance")
  140. if err != nil {
  141. log.Errorf("Unmarshal security group details fail %s", err)
  142. return nil, 0, err
  143. }
  144. total, _ := body.Int("TotalCount")
  145. return instances, int(total), nil
  146. }
  147. func (self *SInstance) GetSecurityGroupIds() ([]string, error) {
  148. return self.SecurityGroupIds.SecurityGroupId, nil
  149. }
  150. func (self *SInstance) GetIHost() cloudprovider.ICloudHost {
  151. return self.host
  152. }
  153. func (self *SInstance) GetId() string {
  154. return self.InstanceId
  155. }
  156. func (self *SInstance) GetName() string {
  157. if len(self.InstanceName) > 0 {
  158. return self.InstanceName
  159. }
  160. return self.InstanceId
  161. }
  162. func (self *SInstance) GetHostname() string {
  163. return self.HostName
  164. }
  165. func (self *SInstance) GetGlobalId() string {
  166. return self.InstanceId
  167. }
  168. func (self *SInstance) IsEmulated() bool {
  169. return false
  170. }
  171. func (self *SInstance) GetInstanceType() string {
  172. return self.InstanceType
  173. }
  174. func (self *SInstance) GetInternetMaxBandwidthOut() int {
  175. return self.InternetMaxBandwidthOut
  176. }
  177. func (self *SInstance) getVpc() (*SVpc, error) {
  178. return self.host.zone.region.getVpc(self.VpcAttributes.VpcId)
  179. }
  180. type byAttachedTime []SDisk
  181. func (a byAttachedTime) Len() int { return len(a) }
  182. func (a byAttachedTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  183. func (a byAttachedTime) Less(i, j int) bool {
  184. switch a[i].GetDiskType() {
  185. case api.DISK_TYPE_SYS:
  186. return true
  187. case api.DISK_TYPE_SWAP:
  188. switch a[j].GetDiskType() {
  189. case api.DISK_TYPE_SYS:
  190. return false
  191. case api.DISK_TYPE_DATA:
  192. return true
  193. }
  194. case api.DISK_TYPE_DATA:
  195. if a[j].GetDiskType() != api.DISK_TYPE_DATA {
  196. return false
  197. }
  198. }
  199. return a[i].AttachedTime.Before(a[j].AttachedTime)
  200. }
  201. func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
  202. disks := []SDisk{}
  203. disks, err := self.host.zone.region.GetDisks(self.InstanceId, "", "", nil, "")
  204. if err != nil {
  205. return nil, errors.Wrapf(err, "GetDisks for %s", self.InstanceId)
  206. }
  207. sort.Sort(byAttachedTime(disks))
  208. idisks := make([]cloudprovider.ICloudDisk, len(disks))
  209. for i := 0; i < len(disks); i += 1 {
  210. store, err := self.host.zone.getStorageByCategory(disks[i].Category)
  211. if err != nil {
  212. return nil, err
  213. }
  214. disks[i].storage = store
  215. idisks[i] = &disks[i]
  216. }
  217. return idisks, nil
  218. }
  219. func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
  220. var (
  221. networkInterfaces = self.NetworkInterfaces.NetworkInterface
  222. nics []cloudprovider.ICloudNic
  223. )
  224. for _, networkInterface := range networkInterfaces {
  225. nic := SInstanceNic{
  226. instance: self,
  227. id: networkInterface.NetworkInterfaceId,
  228. ipAddr: networkInterface.PrimaryIpAddress,
  229. macAddr: networkInterface.MacAddress,
  230. }
  231. nics = append(nics, &nic)
  232. }
  233. return nics, nil
  234. }
  235. func (self *SInstance) GetVcpuCount() int {
  236. return self.Cpu
  237. }
  238. func (self *SInstance) GetVmemSizeMB() int {
  239. return self.Memory
  240. }
  241. func (self *SInstance) GetBootOrder() string {
  242. return "dcn"
  243. }
  244. func (self *SInstance) GetVga() string {
  245. return "std"
  246. }
  247. func (self *SInstance) GetVdi() string {
  248. return "vnc"
  249. }
  250. func (self *SInstance) GetOsType() cloudprovider.TOsType {
  251. return cloudprovider.TOsType(osprofile.NormalizeOSType(self.OSType))
  252. }
  253. func (self *SInstance) GetFullOsName() string {
  254. return self.OSName
  255. }
  256. func (ins *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo {
  257. if ins.osInfo == nil {
  258. osInfo := imagetools.NormalizeImageInfo(ins.OSName, "", ins.OSType, "", "")
  259. ins.osInfo = &osInfo
  260. }
  261. return ins.osInfo
  262. }
  263. func (ins *SInstance) GetBios() cloudprovider.TBiosType {
  264. return cloudprovider.ToBiosType(ins.getNormalizedOsInfo().OsBios)
  265. }
  266. func (ins *SInstance) GetOsArch() string {
  267. return ins.getNormalizedOsInfo().OsArch
  268. }
  269. func (ins *SInstance) GetOsDist() string {
  270. return ins.getNormalizedOsInfo().OsDistro
  271. }
  272. func (ins *SInstance) GetOsVersion() string {
  273. return ins.getNormalizedOsInfo().OsVersion
  274. }
  275. func (ins *SInstance) GetOsLang() string {
  276. return ins.getNormalizedOsInfo().OsLang
  277. }
  278. func (self *SInstance) GetMachine() string {
  279. return "pc"
  280. }
  281. func (self *SInstance) GetStatus() string {
  282. // Running:运行中
  283. //Starting:启动中
  284. //Stopping:停止中
  285. //Stopped:已停止
  286. switch self.Status {
  287. case InstanceStatusRunning:
  288. return api.VM_RUNNING
  289. case InstanceStatusStarting:
  290. return api.VM_STARTING
  291. case InstanceStatusStopping:
  292. return api.VM_STOPPING
  293. case InstanceStatusStopped:
  294. return api.VM_READY
  295. default:
  296. return api.VM_UNKNOWN
  297. }
  298. }
  299. func (self *SInstance) Refresh() error {
  300. new, err := self.host.zone.region.GetInstance(self.InstanceId)
  301. if err != nil {
  302. return err
  303. }
  304. return jsonutils.Update(self, new)
  305. }
  306. func (self *SInstance) GetHypervisor() string {
  307. return api.HYPERVISOR_APSARA
  308. }
  309. func (self *SInstance) StartVM(ctx context.Context) error {
  310. timeout := 300 * time.Second
  311. interval := 15 * time.Second
  312. startTime := time.Now()
  313. for time.Now().Sub(startTime) < timeout {
  314. err := self.Refresh()
  315. if err != nil {
  316. return err
  317. }
  318. log.Debugf("status %s expect %s", self.GetStatus(), api.VM_RUNNING)
  319. if self.GetStatus() == api.VM_RUNNING {
  320. return nil
  321. } else if self.GetStatus() == api.VM_READY {
  322. err := self.host.zone.region.StartVM(self.InstanceId)
  323. if err != nil {
  324. return err
  325. }
  326. }
  327. time.Sleep(interval)
  328. }
  329. return cloudprovider.ErrTimeout
  330. }
  331. func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
  332. err := self.host.zone.region.StopVM(self.InstanceId, opts.IsForce)
  333. if err != nil {
  334. return err
  335. }
  336. return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 300*time.Second) // 5mintues
  337. }
  338. func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
  339. url, err := self.host.zone.region.GetInstanceVNCUrl(self.InstanceId)
  340. if err != nil {
  341. return nil, err
  342. }
  343. passwd := seclib.RandomPassword(6)
  344. err = self.host.zone.region.ModifyInstanceVNCUrlPassword(self.InstanceId, passwd)
  345. if err != nil {
  346. return nil, err
  347. }
  348. ret := &cloudprovider.ServerVncOutput{
  349. Url: url,
  350. Password: passwd,
  351. Protocol: "apsara",
  352. InstanceId: self.InstanceId,
  353. Hypervisor: api.HYPERVISOR_APSARA,
  354. }
  355. return ret, nil
  356. }
  357. func (self *SInstance) UpdateVM(ctx context.Context, input cloudprovider.SInstanceUpdateOptions) error {
  358. return self.host.zone.region.UpdateVM(self.InstanceId, input)
  359. }
  360. func (self *SInstance) DeployVM(ctx context.Context, opts *cloudprovider.SInstanceDeployOptions) error {
  361. return self.host.zone.region.DeployVM(self.InstanceId, opts)
  362. }
  363. func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
  364. keypair := ""
  365. if len(desc.PublicKey) > 0 {
  366. var err error
  367. keypair, err = self.host.zone.region.syncKeypair(desc.PublicKey)
  368. if err != nil {
  369. return "", err
  370. }
  371. }
  372. diskId, err := self.host.zone.region.ReplaceSystemDisk(self.InstanceId, desc.ImageId, desc.Password, keypair, desc.SysSizeGB)
  373. if err != nil {
  374. return "", err
  375. }
  376. return diskId, nil
  377. }
  378. func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
  379. if len(config.InstanceType) > 0 {
  380. return self.host.zone.region.ChangeVMConfig2(self.ZoneId, self.InstanceId, config.InstanceType, nil)
  381. }
  382. return self.host.zone.region.ChangeVMConfig(self.ZoneId, self.InstanceId, config.Cpu, config.MemoryMB, nil)
  383. }
  384. func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
  385. return self.host.zone.region.AttachDisk(self.InstanceId, diskId)
  386. }
  387. func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
  388. return cloudprovider.RetryOnError(
  389. func() error {
  390. return self.host.zone.region.DetachDisk(self.InstanceId, diskId)
  391. },
  392. []string{
  393. `"Code":"InvalidOperation.Conflict"`,
  394. },
  395. 4)
  396. }
  397. func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) {
  398. instances, _, err := self.GetInstances("", []string{instanceId}, 0, 1)
  399. if err != nil {
  400. return nil, err
  401. }
  402. if len(instances) == 0 {
  403. return nil, cloudprovider.ErrNotFound
  404. }
  405. return &instances[0], nil
  406. }
  407. func (self *SRegion) CreateInstance(name, hostname string, imageId string, instanceType string, securityGroupIds []string,
  408. zoneId string, desc string, passwd string, disks []SDisk, vSwitchId string, ipAddr string,
  409. keypair string, userData string, bc *billing.SBillingCycle, projectId, osType string,
  410. tags map[string]string,
  411. ) (string, error) {
  412. params := make(map[string]string)
  413. params["RegionId"] = self.RegionId
  414. params["ImageId"] = imageId
  415. params["InstanceType"] = instanceType
  416. for _, id := range securityGroupIds {
  417. params["SecurityGroupId"] = id
  418. }
  419. params["ZoneId"] = zoneId
  420. params["InstanceName"] = name
  421. if len(hostname) > 0 {
  422. params["HostName"] = hostname
  423. }
  424. params["Description"] = desc
  425. params["InternetChargeType"] = "PayByTraffic"
  426. params["InternetMaxBandwidthIn"] = "200"
  427. params["InternetMaxBandwidthOut"] = "100"
  428. if len(passwd) > 0 {
  429. params["Password"] = passwd
  430. } else {
  431. params["PasswordInherit"] = "True"
  432. }
  433. if len(projectId) > 0 {
  434. params["ResourceGroupId"] = projectId
  435. }
  436. //{"Code":"InvalidSystemDiskCategory.ValueNotSupported","HostId":"ecs.apsaracs.com","Message":"The specified parameter 'SystemDisk.Category' is not support IoOptimized Instance. Valid Values: cloud_efficiency;cloud_ssd. ","RequestId":"9C9A4E99-5196-42A2-80B6-4762F8F75C90"}
  437. params["IoOptimized"] = "optimized"
  438. for i, d := range disks {
  439. if i == 0 {
  440. params["SystemDisk.Category"] = d.Category
  441. if d.Category == api.STORAGE_CLOUD_ESSD_PL2 {
  442. params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD
  443. params["SystemDisk.PerformanceLevel"] = "PL2"
  444. }
  445. if d.Category == api.STORAGE_CLOUD_ESSD_PL3 {
  446. params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD
  447. params["SystemDisk.PerformanceLevel"] = "PL3"
  448. }
  449. params["SystemDisk.Size"] = fmt.Sprintf("%d", d.Size)
  450. params["SystemDisk.DiskName"] = d.GetName()
  451. params["SystemDisk.Description"] = d.Description
  452. } else {
  453. params[fmt.Sprintf("DataDisk.%d.Size", i)] = fmt.Sprintf("%d", d.Size)
  454. params[fmt.Sprintf("DataDisk.%d.Category", i)] = d.Category
  455. if d.Category == api.STORAGE_CLOUD_ESSD_PL2 {
  456. params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD
  457. params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL2"
  458. }
  459. if d.Category == api.STORAGE_CLOUD_ESSD_PL3 {
  460. params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD
  461. params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL3"
  462. }
  463. params[fmt.Sprintf("DataDisk.%d.DiskName", i)] = d.GetName()
  464. params[fmt.Sprintf("DataDisk.%d.Description", i)] = d.Description
  465. params[fmt.Sprintf("DataDisk.%d.Encrypted", i)] = "false"
  466. }
  467. }
  468. params["VSwitchId"] = vSwitchId
  469. params["PrivateIpAddress"] = ipAddr
  470. if len(keypair) > 0 {
  471. params["KeyPairName"] = keypair
  472. }
  473. if len(userData) > 0 {
  474. params["UserData"] = userData
  475. }
  476. if len(tags) > 0 {
  477. tagIdx := 0
  478. for k, v := range tags {
  479. params[fmt.Sprintf("Tag.%d.Key", tagIdx)] = k
  480. params[fmt.Sprintf("Tag.%d.Value", tagIdx)] = v
  481. tagIdx += 1
  482. }
  483. }
  484. if bc != nil {
  485. params["InstanceChargeType"] = "PrePaid"
  486. err := billingCycle2Params(bc, params)
  487. if err != nil {
  488. return "", err
  489. }
  490. if bc.AutoRenew {
  491. params["AutoRenew"] = "true"
  492. params["AutoRenewPeriod"] = "1"
  493. } else {
  494. params["AutoRenew"] = "False"
  495. }
  496. } else {
  497. params["InstanceChargeType"] = "PostPaid"
  498. params["SpotStrategy"] = "NoSpot"
  499. }
  500. params["ClientToken"] = utils.GenRequestId(20)
  501. body, err := self.ecsRequest("CreateInstance", params)
  502. if err != nil {
  503. log.Errorf("CreateInstance fail %s", err)
  504. return "", err
  505. }
  506. instanceId, _ := body.GetString("InstanceId")
  507. return instanceId, nil
  508. }
  509. func (self *SRegion) doStartVM(instanceId string) error {
  510. return self.instanceOperation(instanceId, "StartInstance", nil)
  511. }
  512. func (self *SRegion) doStopVM(instanceId string, isForce bool) error {
  513. params := make(map[string]string)
  514. if isForce {
  515. params["ForceStop"] = "true"
  516. } else {
  517. params["ForceStop"] = "false"
  518. }
  519. params["StoppedMode"] = "KeepCharging"
  520. return self.instanceOperation(instanceId, "StopInstance", params)
  521. }
  522. func (self *SRegion) doDeleteVM(instanceId string) error {
  523. params := make(map[string]string)
  524. params["TerminateSubscription"] = "true" // terminate expired prepaid instance
  525. params["Force"] = "true"
  526. return self.instanceOperation(instanceId, "DeleteInstance", params)
  527. }
  528. /*func (self *SRegion) waitInstanceStatus(instanceId string, target string, interval time.Duration, timeout time.Duration) error {
  529. startTime := time.Now()
  530. for time.Now().Sub(startTime) < timeout {
  531. status, err := self.GetInstanceStatus(instanceId)
  532. if err != nil {
  533. return err
  534. }
  535. if status == target {
  536. return nil
  537. }
  538. time.Sleep(interval)
  539. }
  540. return cloudprovider.ErrTimeout
  541. }
  542. func (self *SInstance) waitStatus(target string, interval time.Duration, timeout time.Duration) error {
  543. return self.host.zone.region.waitInstanceStatus(self.InstanceId, target, interval, timeout)
  544. }*/
  545. func (self *SRegion) StartVM(instanceId string) error {
  546. status, err := self.GetInstanceStatus(instanceId)
  547. if err != nil {
  548. log.Errorf("Fail to get instance status on StartVM: %s", err)
  549. return err
  550. }
  551. if status != InstanceStatusStopped {
  552. log.Errorf("StartVM: vm status is %s expect %s", status, InstanceStatusStopped)
  553. return cloudprovider.ErrInvalidStatus
  554. }
  555. return self.doStartVM(instanceId)
  556. // if err != nil {
  557. // return err
  558. // }
  559. // return self.waitInstanceStatus(instanceId, InstanceStatusRunning, time.Second*5, time.Second*180) // 3 minutes to timeout
  560. }
  561. func (self *SRegion) StopVM(instanceId string, isForce bool) error {
  562. status, err := self.GetInstanceStatus(instanceId)
  563. if err != nil {
  564. log.Errorf("Fail to get instance status on StopVM: %s", err)
  565. return err
  566. }
  567. if status == InstanceStatusStopped {
  568. return nil
  569. }
  570. if status != InstanceStatusRunning {
  571. log.Errorf("StopVM: vm status is %s expect %s", status, InstanceStatusRunning)
  572. return cloudprovider.ErrInvalidStatus
  573. }
  574. return self.doStopVM(instanceId, isForce)
  575. // if err != nil {
  576. // return err
  577. // }
  578. // return self.waitInstanceStatus(instanceId, InstanceStatusStopped, time.Second*10, time.Second*300) // 5 minutes to timeout
  579. }
  580. func (self *SRegion) DeleteVM(instanceId string) error {
  581. status, err := self.GetInstanceStatus(instanceId)
  582. if err != nil {
  583. log.Errorf("Fail to get instance status on DeleteVM: %s", err)
  584. return err
  585. }
  586. log.Debugf("Instance status on delete is %s", status)
  587. if status != InstanceStatusStopped {
  588. log.Warningf("DeleteVM: vm status is %s expect %s", status, InstanceStatusStopped)
  589. }
  590. return self.doDeleteVM(instanceId)
  591. // if err != nil {
  592. // return err
  593. // }
  594. // err = self.waitInstanceStatus(instanceId, InstanceStatusRunning, time.Second*10, time.Second*300) // 5 minutes to timeout
  595. // if err == cloudprovider.ErrNotFound {
  596. // return nil
  597. // } else if err == nil {
  598. // return cloudprovider.ErrTimeout
  599. // } else {
  600. // return err
  601. // }
  602. }
  603. func (self *SRegion) DeployVM(instanceId string, opts *cloudprovider.SInstanceDeployOptions) error {
  604. instance, err := self.GetInstance(instanceId)
  605. if err != nil {
  606. return err
  607. }
  608. // 修改密钥时直接返回
  609. if opts.DeleteKeypair {
  610. err = self.DetachKeyPair(instanceId, instance.KeyPairName)
  611. if err != nil {
  612. return err
  613. }
  614. }
  615. var keypairName string
  616. if len(opts.PublicKey) > 0 {
  617. var err error
  618. keypairName, err = self.syncKeypair(opts.PublicKey)
  619. if err != nil {
  620. return err
  621. }
  622. err = self.AttachKeypair(instanceId, keypairName)
  623. if err != nil {
  624. return err
  625. }
  626. }
  627. // 指定密码的情况下,使用指定的密码
  628. if len(opts.Password) > 0 {
  629. params := make(map[string]string)
  630. params["Password"] = opts.Password
  631. return self.modifyInstanceAttribute(instanceId, params)
  632. }
  633. return nil
  634. }
  635. func (self *SInstance) DeleteVM(ctx context.Context) error {
  636. for {
  637. err := self.host.zone.region.DeleteVM(self.InstanceId)
  638. if err != nil {
  639. if isError(err, "IncorrectInstanceStatus.Initializing") {
  640. log.Infof("The instance is initializing, try later ...")
  641. time.Sleep(10 * time.Second)
  642. } else {
  643. log.Errorf("DeleteVM fail: %s", err)
  644. return err
  645. }
  646. } else {
  647. break
  648. }
  649. }
  650. return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes
  651. }
  652. func (self *SRegion) UpdateVM(instanceId string, input cloudprovider.SInstanceUpdateOptions) error {
  653. /*
  654. api: ModifyInstanceAttribute
  655. https://help.apsara.com/document_detail/25503.html?spm=a2c4g.11186623.4.1.DrgpjW
  656. */
  657. params := make(map[string]string)
  658. params["InstanceName"] = input.NAME
  659. params["Description"] = input.Description
  660. return self.modifyInstanceAttribute(instanceId, params)
  661. }
  662. func (self *SRegion) modifyInstanceAttribute(instanceId string, params map[string]string) error {
  663. params["x-acs-instanceid"] = instanceId
  664. return self.instanceOperation(instanceId, "ModifyInstanceAttribute", params)
  665. }
  666. func (self *SRegion) ReplaceSystemDisk(instanceId string, imageId string, passwd string, keypairName string, sysDiskSizeGB int) (string, error) {
  667. params := make(map[string]string)
  668. params["RegionId"] = self.RegionId
  669. params["InstanceId"] = instanceId
  670. params["ImageId"] = imageId
  671. if len(passwd) > 0 {
  672. params["Password"] = passwd
  673. } else {
  674. params["PasswordInherit"] = "True"
  675. }
  676. if len(keypairName) > 0 {
  677. params["KeyPairName"] = keypairName
  678. }
  679. if sysDiskSizeGB > 0 {
  680. params["SystemDisk.Size"] = fmt.Sprintf("%d", sysDiskSizeGB)
  681. }
  682. params["x-acs-instanceid"] = instanceId
  683. body, err := self.ecsRequest("ReplaceSystemDisk", params)
  684. if err != nil {
  685. return "", err
  686. }
  687. // log.Debugf("%s", body.String())
  688. return body.GetString("DiskId")
  689. }
  690. func (self *SRegion) ChangeVMConfig(zoneId string, instanceId string, ncpu int, vmem int, disks []*SDisk) error {
  691. // todo: support change disk config?
  692. params := make(map[string]string)
  693. instanceTypes, e := self.GetMatchInstanceTypes(ncpu, vmem, 0, zoneId)
  694. if e != nil {
  695. return e
  696. }
  697. for _, instancetype := range instanceTypes {
  698. params["InstanceType"] = instancetype.InstanceTypeId
  699. params["ClientToken"] = utils.GenRequestId(20)
  700. if err := self.instanceOperation(instanceId, "ModifyInstanceSpec", params); err != nil {
  701. log.Errorf("Failed for %s: %s", instancetype.InstanceTypeId, err)
  702. } else {
  703. return nil
  704. }
  705. }
  706. return fmt.Errorf("Failed to change vm config, specification not supported")
  707. }
  708. func (self *SRegion) ChangeVMConfig2(zoneId string, instanceId string, instanceType string, disks []*SDisk) error {
  709. // todo: support change disk config?
  710. params := make(map[string]string)
  711. params["InstanceType"] = instanceType
  712. params["ClientToken"] = utils.GenRequestId(20)
  713. if err := self.instanceOperation(instanceId, "ModifyInstanceSpec", params); err != nil {
  714. log.Errorf("Failed for %s: %s", instanceType, err)
  715. return fmt.Errorf("Failed to change vm config, specification not supported")
  716. } else {
  717. return nil
  718. }
  719. }
  720. func (self *SRegion) DetachDisk(instanceId string, diskId string) error {
  721. params := make(map[string]string)
  722. params["InstanceId"] = instanceId
  723. params["DiskId"] = diskId
  724. log.Infof("Detach instance %s disk %s", instanceId, diskId)
  725. params["x-acs-instanceid"] = instanceId
  726. _, err := self.ecsRequest("DetachDisk", params)
  727. if err != nil {
  728. if strings.Contains(err.Error(), "The specified disk has not been attached on the specified instance") {
  729. return nil
  730. }
  731. return errors.Wrap(err, "DetachDisk")
  732. }
  733. return nil
  734. }
  735. func (self *SRegion) AttachDisk(instanceId string, diskId string) error {
  736. params := make(map[string]string)
  737. params["InstanceId"] = instanceId
  738. params["DiskId"] = diskId
  739. params["x-acs-instanceid"] = instanceId
  740. _, err := self.ecsRequest("AttachDisk", params)
  741. if err != nil {
  742. log.Errorf("AttachDisk %s to %s fail %s", diskId, instanceId, err)
  743. return err
  744. }
  745. return nil
  746. }
  747. func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
  748. if len(self.EipAddress.IpAddress) > 0 {
  749. return self.host.zone.region.GetEip(self.EipAddress.AllocationId)
  750. }
  751. if len(self.PublicIpAddress.IpAddress) > 0 {
  752. eip := SEipAddress{}
  753. eip.region = self.host.zone.region
  754. eip.IpAddress = self.PublicIpAddress.IpAddress[0]
  755. eip.InstanceId = self.InstanceId
  756. eip.InstanceType = EIP_INSTANCE_TYPE_ECS
  757. eip.Status = EIP_STATUS_INUSE
  758. eip.AllocationId = self.InstanceId // fixed
  759. eip.AllocationTime = self.CreationTime
  760. eip.Bandwidth = self.InternetMaxBandwidthOut
  761. eip.InternetChargeType = self.InternetChargeType
  762. return &eip, nil
  763. }
  764. return nil, nil
  765. }
  766. func (self *SInstance) SetSecurityGroups(secgroupIds []string) error {
  767. return self.host.zone.region.SetSecurityGroups(secgroupIds, self.InstanceId)
  768. }
  769. func (self *SInstance) GetBillingType() string {
  770. return convertChargeType(self.InstanceChargeType)
  771. }
  772. func (self *SInstance) GetCreatedAt() time.Time {
  773. return self.CreationTime
  774. }
  775. func (self *SInstance) UpdateUserData(userData string) error {
  776. return self.host.zone.region.updateInstance(self.InstanceId, "", "", "", "", userData)
  777. }
  778. func (self *SInstance) Renew(bc billing.SBillingCycle) error {
  779. return self.host.zone.region.RenewInstance(self.InstanceId, bc)
  780. }
  781. func billingCycle2Params(bc *billing.SBillingCycle, params map[string]string) error {
  782. if bc.GetMonths() > 0 {
  783. params["PeriodUnit"] = "Month"
  784. params["Period"] = fmt.Sprintf("%d", bc.GetMonths())
  785. } else if bc.GetWeeks() > 0 {
  786. params["PeriodUnit"] = "Week"
  787. params["Period"] = fmt.Sprintf("%d", bc.GetWeeks())
  788. } else {
  789. return fmt.Errorf("invalid renew time period %s", bc.String())
  790. }
  791. return nil
  792. }
  793. func (region *SRegion) RenewInstance(instanceId string, bc billing.SBillingCycle) error {
  794. params := make(map[string]string)
  795. params["InstanceId"] = instanceId
  796. err := billingCycle2Params(&bc, params)
  797. if err != nil {
  798. return err
  799. }
  800. params["ClientToken"] = utils.GenRequestId(20)
  801. params["x-acs-instanceid"] = instanceId
  802. _, err = region.ecsRequest("RenewInstance", params)
  803. if err != nil {
  804. log.Errorf("RenewInstance fail %s", err)
  805. return err
  806. }
  807. return nil
  808. }
  809. func (self *SInstance) GetError() error {
  810. return nil
  811. }
  812. func (region *SRegion) ConvertPublicIpToEip(instanceId string) error {
  813. params := make(map[string]string)
  814. params["InstanceId"] = instanceId
  815. params["RegionId"] = region.RegionId
  816. _, err := region.ecsRequest("ConvertNatPublicIpToEip", params)
  817. return err
  818. }
  819. func (self *SInstance) ConvertPublicIpToEip() error {
  820. return self.host.zone.region.ConvertPublicIpToEip(self.InstanceId)
  821. }
  822. func (region *SRegion) SetInstanceAutoRenew(instanceId string, autoRenew bool) error {
  823. params := make(map[string]string)
  824. params["InstanceId"] = instanceId
  825. params["RegionId"] = region.RegionId
  826. if autoRenew {
  827. params["RenewalStatus"] = "AutoRenewal"
  828. params["Duration"] = "1"
  829. } else {
  830. params["RenewalStatus"] = "Normal"
  831. }
  832. params["x-acs-instanceid"] = instanceId
  833. _, err := region.ecsRequest("ModifyInstanceAutoRenewAttribute", params)
  834. return err
  835. }
  836. type SAutoRenewAttr struct {
  837. Duration int
  838. AutoRenewEnabled bool
  839. RenewalStatus string
  840. PeriodUnit string
  841. }
  842. func (region *SRegion) GetInstanceAutoRenewAttribute(instanceId string) (*SAutoRenewAttr, error) {
  843. params := make(map[string]string)
  844. params["InstanceId"] = instanceId
  845. params["RegionId"] = region.RegionId
  846. resp, err := region.ecsRequest("DescribeInstanceAutoRenewAttribute", params)
  847. if err != nil {
  848. return nil, errors.Wrap(err, "DescribeInstanceAutoRenewAttribute")
  849. }
  850. attr := []SAutoRenewAttr{}
  851. err = resp.Unmarshal(&attr, "InstanceRenewAttributes", "InstanceRenewAttribute")
  852. if err != nil {
  853. return nil, errors.Wrap(err, "resp.Unmarshal")
  854. }
  855. if len(attr) == 1 {
  856. return &attr[0], nil
  857. }
  858. return nil, fmt.Errorf("get %d auto renew info", len(attr))
  859. }
  860. func (self *SInstance) IsAutoRenew() bool {
  861. attr, err := self.host.zone.region.GetInstanceAutoRenewAttribute(self.InstanceId)
  862. if err != nil {
  863. log.Errorf("failed to get instance %s auto renew info", self.InstanceId)
  864. return false
  865. }
  866. return attr.AutoRenewEnabled
  867. }
  868. func (self *SInstance) SetAutoRenew(bc billing.SBillingCycle) error {
  869. return self.host.zone.region.SetInstanceAutoRenew(self.InstanceId, bc.AutoRenew)
  870. }
  871. func (self *SInstance) SetTags(tags map[string]string, replace bool) error {
  872. return self.host.zone.region.SetResourceTags("ecs", "instance", []string{self.InstanceId}, tags, replace)
  873. }