instance.go 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281
  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 aliyun
  15. import (
  16. "context"
  17. "fmt"
  18. "sort"
  19. "strings"
  20. "time"
  21. alierr "github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/errors"
  25. "yunion.io/x/pkg/util/billing"
  26. "yunion.io/x/pkg/util/imagetools"
  27. "yunion.io/x/pkg/util/osprofile"
  28. "yunion.io/x/pkg/utils"
  29. billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
  30. api "yunion.io/x/cloudmux/pkg/apis/compute"
  31. "yunion.io/x/cloudmux/pkg/cloudprovider"
  32. "yunion.io/x/cloudmux/pkg/multicloud"
  33. )
  34. const (
  35. // Running:运行中
  36. //Starting:启动中
  37. //Stopping:停止中
  38. //Stopped:已停止
  39. InstanceStatusStopped = "Stopped"
  40. InstanceStatusRunning = "Running"
  41. InstanceStatusStopping = "Stopping"
  42. InstanceStatusStarting = "Starting"
  43. )
  44. type SDedicatedHostAttribute struct {
  45. DedicatedHostId string
  46. DedicatedHostName string
  47. }
  48. type SIpAddress struct {
  49. IpAddress []string
  50. }
  51. type SNetworkInterfaces struct {
  52. NetworkInterface []SNetworkInterface
  53. }
  54. type SOperationLocks struct {
  55. LockReason []string
  56. }
  57. type SSecurityGroupIds struct {
  58. SecurityGroupId []string
  59. }
  60. // {"NatIpAddress":"","PrivateIpAddress":{"IpAddress":["192.168.220.214"]},"VSwitchId":"vsw-2ze9cqwza4upoyujq1thd","VpcId":"vpc-2zer4jy8ix3i8f0coc5uw"}
  61. type SVpcAttributes struct {
  62. NatIpAddress string
  63. PrivateIpAddress SIpAddress
  64. VSwitchId string
  65. VpcId string
  66. }
  67. type SInstance struct {
  68. multicloud.SInstanceBase
  69. AliyunTags
  70. host *SHost
  71. osInfo *imagetools.ImageInfo
  72. // idisks []cloudprovider.ICloudDisk
  73. AutoReleaseTime string
  74. ClusterId string
  75. Cpu int
  76. CreationTime time.Time
  77. DedicatedHostAttribute SDedicatedHostAttribute
  78. Description string
  79. DeviceAvailable bool
  80. EipAddress SEipAddress
  81. ExpiredTime time.Time
  82. GPUAmount int
  83. GPUSpec string
  84. HostName string
  85. ImageId string
  86. InnerIpAddress SIpAddress
  87. InstanceChargeType TChargeType
  88. InstanceId string
  89. InstanceName string
  90. InstanceNetworkType string
  91. InstanceType string
  92. InstanceTypeFamily string
  93. InternetChargeType TInternetChargeType
  94. InternetMaxBandwidthIn int
  95. InternetMaxBandwidthOut int
  96. IoOptimized bool
  97. KeyPairName string
  98. Memory int
  99. NetworkInterfaces SNetworkInterfaces
  100. OSName string
  101. OSType string
  102. OperationLocks SOperationLocks
  103. PublicIpAddress SIpAddress
  104. Recyclable bool
  105. RegionId string
  106. ResourceGroupId string
  107. SaleCycle string
  108. SecurityGroupIds SSecurityGroupIds
  109. SerialNumber string
  110. SpotPriceLimit string
  111. SpotStrategy string
  112. StartTime time.Time
  113. Status string
  114. StoppedMode string
  115. VlanId string
  116. VpcAttributes SVpcAttributes
  117. ZoneId string
  118. Throughput int
  119. }
  120. // {"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"}
  121. func (self *SRegion) GetInstances(zoneId string, ids []string) ([]SInstance, error) {
  122. params := make(map[string]string)
  123. params["RegionId"] = self.RegionId
  124. params["MaxResults"] = "100"
  125. params["AdditionalAttributes.1"] = "NETWORK_PRIMARY_ENI_IP"
  126. if len(zoneId) > 0 {
  127. params["ZoneId"] = zoneId
  128. }
  129. if ids != nil && len(ids) > 0 {
  130. params["InstanceIds"] = jsonutils.Marshal(ids).String()
  131. }
  132. ret := make([]SInstance, 0)
  133. for {
  134. resp, err := self.ecsRequest("DescribeInstances", params)
  135. if err != nil {
  136. return nil, errors.Wrapf(err, "DescribeInstances")
  137. }
  138. part := struct {
  139. Instances struct {
  140. Instance []SInstance
  141. }
  142. NextToken string
  143. }{}
  144. err = resp.Unmarshal(&part)
  145. if err != nil {
  146. return nil, errors.Wrapf(err, "Unmarshal")
  147. }
  148. ret = append(ret, part.Instances.Instance...)
  149. if len(part.Instances.Instance) == 0 || len(part.NextToken) == 0 {
  150. break
  151. }
  152. params["NextToken"] = part.NextToken
  153. }
  154. return ret, nil
  155. }
  156. func (self *SInstance) GetSecurityGroupIds() ([]string, error) {
  157. return self.SecurityGroupIds.SecurityGroupId, nil
  158. }
  159. func (self *SInstance) GetIHost() cloudprovider.ICloudHost {
  160. return self.host
  161. }
  162. func (self *SInstance) GetId() string {
  163. return self.InstanceId
  164. }
  165. func (self *SInstance) GetName() string {
  166. if len(self.InstanceName) > 0 {
  167. return self.InstanceName
  168. }
  169. return self.InstanceId
  170. }
  171. func (self *SInstance) GetDescription() string {
  172. return self.Description
  173. }
  174. func (self *SInstance) GetHostname() string {
  175. return self.HostName
  176. }
  177. func (self *SInstance) GetGlobalId() string {
  178. return self.InstanceId
  179. }
  180. func (self *SInstance) IsEmulated() bool {
  181. return false
  182. }
  183. func (self *SInstance) GetInstanceType() string {
  184. return self.InstanceType
  185. }
  186. func (self *SInstance) getVpc() (*SVpc, error) {
  187. return self.host.zone.region.getVpc(self.VpcAttributes.VpcId)
  188. }
  189. type byAttachedTime []SDisk
  190. func (a byAttachedTime) Len() int { return len(a) }
  191. func (a byAttachedTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  192. func (a byAttachedTime) Less(i, j int) bool {
  193. switch a[i].GetDiskType() {
  194. case api.DISK_TYPE_SYS:
  195. return true
  196. case api.DISK_TYPE_SWAP:
  197. switch a[j].GetDiskType() {
  198. case api.DISK_TYPE_SYS:
  199. return false
  200. case api.DISK_TYPE_DATA:
  201. return true
  202. }
  203. case api.DISK_TYPE_DATA:
  204. if a[j].GetDiskType() != api.DISK_TYPE_DATA {
  205. return false
  206. }
  207. }
  208. return a[i].AttachedTime.Before(a[j].AttachedTime)
  209. }
  210. func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
  211. disks, err := self.host.zone.region.GetDisks(self.InstanceId, "", "", nil, "")
  212. if err != nil {
  213. return nil, errors.Wrapf(err, "GetDisks for %s", self.InstanceId)
  214. }
  215. sort.Sort(byAttachedTime(disks))
  216. idisks := make([]cloudprovider.ICloudDisk, len(disks))
  217. for i := 0; i < len(disks); i += 1 {
  218. store, err := self.host.zone.getStorageByCategory(disks[i].Category)
  219. if err != nil {
  220. return nil, err
  221. }
  222. disks[i].storage = store
  223. idisks[i] = &disks[i]
  224. }
  225. return idisks, nil
  226. }
  227. func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
  228. var (
  229. networkInterfaces = self.NetworkInterfaces.NetworkInterface
  230. nics []cloudprovider.ICloudNic
  231. )
  232. for _, networkInterface := range networkInterfaces {
  233. nic := SInstanceNic{
  234. instance: self,
  235. id: networkInterface.NetworkInterfaceId,
  236. ipAddr: networkInterface.PrimaryIpAddress,
  237. macAddr: networkInterface.MacAddress,
  238. }
  239. for _, ipv6 := range networkInterface.Ipv6Sets.Ipv6Set {
  240. if len(ipv6.Ipv6Address) > 0 {
  241. nic.ip6Addr = ipv6.Ipv6Address
  242. }
  243. }
  244. nics = append(nics, &nic)
  245. }
  246. for _, classicIp := range self.InnerIpAddress.IpAddress {
  247. nic := SInstanceNic{
  248. instance: self,
  249. id: fmt.Sprintf("%s-%s", self.InstanceId, classicIp),
  250. ipAddr: classicIp,
  251. classic: true,
  252. }
  253. nics = append(nics, &nic)
  254. }
  255. return nics, nil
  256. }
  257. func (self *SInstance) GetVcpuCount() int {
  258. return self.Cpu
  259. }
  260. func (self *SInstance) GetVmemSizeMB() int {
  261. return self.Memory
  262. }
  263. func (self *SInstance) GetBootOrder() string {
  264. return "dcn"
  265. }
  266. func (self *SInstance) GetVga() string {
  267. return "std"
  268. }
  269. func (self *SInstance) GetVdi() string {
  270. return "vnc"
  271. }
  272. func (ins *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo {
  273. if ins.osInfo == nil {
  274. osInfo := imagetools.NormalizeImageInfo(ins.OSName, "", ins.OSType, "", "")
  275. ins.osInfo = &osInfo
  276. }
  277. return ins.osInfo
  278. }
  279. func (self *SInstance) GetOsType() cloudprovider.TOsType {
  280. return cloudprovider.TOsType(osprofile.NormalizeOSType(self.OSType))
  281. }
  282. func (self *SInstance) GetFullOsName() string {
  283. return self.OSName
  284. }
  285. func (ins *SInstance) GetBios() cloudprovider.TBiosType {
  286. return cloudprovider.ToBiosType(ins.getNormalizedOsInfo().OsBios)
  287. }
  288. func (ins *SInstance) GetOsArch() string {
  289. return ins.getNormalizedOsInfo().OsArch
  290. }
  291. func (ins *SInstance) GetOsDist() string {
  292. return ins.getNormalizedOsInfo().OsDistro
  293. }
  294. func (ins *SInstance) GetOsVersion() string {
  295. return ins.getNormalizedOsInfo().OsVersion
  296. }
  297. func (ins *SInstance) GetOsLang() string {
  298. return ins.getNormalizedOsInfo().OsLang
  299. }
  300. func (self *SInstance) GetMachine() string {
  301. return "pc"
  302. }
  303. func (self *SInstance) GetStatus() string {
  304. // Running:运行中
  305. //Starting:启动中
  306. //Stopping:停止中
  307. //Stopped:已停止
  308. switch self.Status {
  309. case InstanceStatusRunning:
  310. return api.VM_RUNNING
  311. case InstanceStatusStarting:
  312. return api.VM_STARTING
  313. case InstanceStatusStopping:
  314. return api.VM_STOPPING
  315. case InstanceStatusStopped:
  316. return api.VM_READY
  317. default:
  318. return api.VM_UNKNOWN
  319. }
  320. }
  321. type SInstanceStatus struct {
  322. InstanceId string
  323. HealthStatus struct {
  324. Name string
  325. }
  326. }
  327. func (region *SRegion) DescribeInstancesFullStatus(vmId string) (*SInstanceStatus, error) {
  328. params := map[string]string{
  329. "InstanceId": vmId,
  330. }
  331. resp, err := region.ecsRequest("DescribeInstancesFullStatus", params)
  332. if err != nil {
  333. return nil, err
  334. }
  335. ret := struct {
  336. InstanceFullStatusSet struct {
  337. InstanceFullStatusType []SInstanceStatus
  338. }
  339. }{}
  340. err = resp.Unmarshal(&ret)
  341. if err != nil {
  342. return nil, errors.Wrapf(err, "Unmarshal")
  343. }
  344. for i := range ret.InstanceFullStatusSet.InstanceFullStatusType {
  345. if ret.InstanceFullStatusSet.InstanceFullStatusType[i].InstanceId == vmId {
  346. return &ret.InstanceFullStatusSet.InstanceFullStatusType[i], nil
  347. }
  348. }
  349. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s", vmId)
  350. }
  351. func (self *SInstance) GetHealthStatus() string {
  352. status, err := self.host.zone.region.DescribeInstancesFullStatus(self.InstanceId)
  353. if err != nil {
  354. return ""
  355. }
  356. return strings.ToLower(status.HealthStatus.Name)
  357. }
  358. func (self *SInstance) Refresh() error {
  359. ins, err := self.host.zone.region.GetInstance(self.InstanceId)
  360. if err != nil {
  361. return errors.Wrapf(err, "GetInstance %s", self.InstanceId)
  362. }
  363. return jsonutils.Update(self, ins)
  364. }
  365. func (self *SInstance) GetHypervisor() string {
  366. return api.HYPERVISOR_ALIYUN
  367. }
  368. func (self *SInstance) StartVM(ctx context.Context) error {
  369. return self.host.zone.region.StartVM(self.InstanceId)
  370. }
  371. func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
  372. err := self.host.zone.region.StopVM(self.InstanceId, opts.IsForce, opts.StopCharging)
  373. if err != nil {
  374. return errors.Wrapf(err, "StopVM %s", self.InstanceId)
  375. }
  376. return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 5*time.Minute)
  377. }
  378. func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
  379. url, err := self.host.zone.region.GetInstanceVNCUrl(self.InstanceId)
  380. if err != nil {
  381. return nil, err
  382. }
  383. ret := &cloudprovider.ServerVncOutput{
  384. Url: url,
  385. Protocol: "aliyun",
  386. InstanceId: self.InstanceId,
  387. Hypervisor: api.HYPERVISOR_ALIYUN,
  388. OsName: string(self.GetOsType()),
  389. }
  390. return ret, nil
  391. }
  392. func (self *SInstance) UpdateVM(ctx context.Context, input cloudprovider.SInstanceUpdateOptions) error {
  393. return self.host.zone.region.UpdateVM(self.InstanceId, input, self.OSType)
  394. }
  395. func (self *SInstance) DeployVM(ctx context.Context, opts *cloudprovider.SInstanceDeployOptions) error {
  396. return self.host.zone.region.DeployVM(self.InstanceId, opts)
  397. }
  398. func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
  399. keypair := ""
  400. if len(desc.PublicKey) > 0 {
  401. var err error
  402. keypair, err = self.host.zone.region.syncKeypair(desc.PublicKey)
  403. if err != nil {
  404. return "", err
  405. }
  406. }
  407. diskId, err := self.host.zone.region.ReplaceSystemDisk(self.InstanceId, desc.ImageId, desc.Password, keypair, desc.SysSizeGB)
  408. if err != nil {
  409. return "", err
  410. }
  411. return diskId, nil
  412. }
  413. func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
  414. isDowngrade, isPrepaid := false, self.GetBillingType() == billing_api.BILLING_TYPE_PREPAID
  415. if (self.GetVcpuCount() > config.Cpu && config.Cpu > 0) || (self.GetVmemSizeMB() > config.MemoryMB && config.MemoryMB > 0) {
  416. isDowngrade = true
  417. }
  418. instanceTypes := []string{}
  419. if len(config.InstanceType) > 0 {
  420. instanceTypes = []string{config.InstanceType}
  421. } else {
  422. specs, err := self.host.zone.region.GetMatchInstanceTypes(config.Cpu, config.MemoryMB, 0, self.ZoneId)
  423. if err != nil {
  424. return errors.Wrapf(err, "GetMatchInstanceTypes")
  425. }
  426. for _, spec := range specs {
  427. instanceTypes = append(instanceTypes, spec.InstanceTypeId)
  428. }
  429. }
  430. var err error
  431. for _, instanceType := range instanceTypes {
  432. if isPrepaid {
  433. err = self.host.zone.region.ChangePrepaidVMConfig(self.InstanceId, instanceType, isDowngrade)
  434. if err != nil {
  435. log.Errorf("ChangePrepaidVMConfig %s error: %v", instanceType, err)
  436. continue
  437. }
  438. } else {
  439. err = self.host.zone.region.ChangeVMConfig(self.InstanceId, instanceType)
  440. if err != nil {
  441. log.Errorf("ChangeVMConfig %s error: %v", instanceType, err)
  442. continue
  443. }
  444. }
  445. return nil
  446. }
  447. if err != nil {
  448. return errors.Wrapf(err, "ChangeVMConfig")
  449. }
  450. return fmt.Errorf("Failed to change vm config, specification not supported")
  451. }
  452. func (self *SInstance) GetModificationTypes() ([]cloudprovider.SInstanceModificationType, error) {
  453. return self.host.zone.region.GetInstanceModificationTypes(self.InstanceId)
  454. }
  455. func (self *SRegion) GetInstanceModificationTypes(instanceId string) ([]cloudprovider.SInstanceModificationType, error) {
  456. params := map[string]string{
  457. "RegionId": self.RegionId,
  458. "ResourceId": instanceId,
  459. "DestinationResource": "InstanceType",
  460. }
  461. body, err := self.ecsRequest("DescribeResourcesModification", params)
  462. if err != nil {
  463. return nil, errors.Wrapf(err, "DescribeResourcesModification")
  464. }
  465. parsed := struct {
  466. AvailableZones struct {
  467. AvailableZone []struct {
  468. AvailableResources struct {
  469. AvailableResource []struct {
  470. SupportedResources struct {
  471. SupportedResource []struct {
  472. Value string
  473. Status string
  474. StatusCategory string
  475. }
  476. }
  477. }
  478. }
  479. }
  480. }
  481. }{}
  482. if err := body.Unmarshal(&parsed); err != nil {
  483. return nil, errors.Wrapf(err, "body.Unmarshal")
  484. }
  485. ret := make([]cloudprovider.SInstanceModificationType, 0)
  486. seen := map[string]struct{}{}
  487. for _, zone := range parsed.AvailableZones.AvailableZone {
  488. for _, resource := range zone.AvailableResources.AvailableResource {
  489. for _, supportedResource := range resource.SupportedResources.SupportedResource {
  490. if len(supportedResource.Value) == 0 || len(supportedResource.Status) == 0 || strings.ToLower(supportedResource.Status) != "available" {
  491. continue
  492. }
  493. if _, ok := seen[supportedResource.Value]; ok {
  494. continue
  495. }
  496. seen[supportedResource.Value] = struct{}{}
  497. ret = append(ret, cloudprovider.SInstanceModificationType{InstanceType: supportedResource.Value})
  498. }
  499. }
  500. }
  501. return ret, nil
  502. }
  503. func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
  504. return self.host.zone.region.AttachDisk(self.InstanceId, diskId)
  505. }
  506. func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
  507. return cloudprovider.RetryOnError(
  508. func() error {
  509. return self.host.zone.region.DetachDisk(self.InstanceId, diskId)
  510. },
  511. []string{
  512. `"Code":"InvalidOperation.Conflict"`,
  513. },
  514. 4)
  515. }
  516. func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) {
  517. instances, err := self.GetInstances("", []string{instanceId})
  518. if err != nil {
  519. return nil, err
  520. }
  521. for i := range instances {
  522. if instances[i].InstanceId == instanceId {
  523. return &instances[i], nil
  524. }
  525. }
  526. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s", instanceId)
  527. }
  528. func (self *SRegion) CreateInstance(name, hostname string, imageId string, instanceType string, securityGroupIds []string,
  529. zoneId string, desc string, passwd string, disks []SDisk, vSwitchId string, ipAddr string,
  530. keypair string, userData string, bc *billing.SBillingCycle, projectId, osType string,
  531. tags map[string]string, publicIp cloudprovider.SPublicIpInfo,
  532. ) (string, error) {
  533. params := make(map[string]string)
  534. params["RegionId"] = self.RegionId
  535. params["ImageId"] = imageId
  536. params["InstanceType"] = instanceType
  537. for _, id := range securityGroupIds {
  538. params["SecurityGroupId"] = id
  539. }
  540. params["ZoneId"] = zoneId
  541. params["InstanceName"] = name
  542. if len(hostname) > 0 {
  543. params["HostName"] = hostname
  544. }
  545. params["Description"] = desc
  546. params["InternetChargeType"] = "PayByTraffic"
  547. if publicIp.PublicIpBw > 0 {
  548. params["InternetMaxBandwidthOut"] = fmt.Sprintf("%d", publicIp.PublicIpBw)
  549. params["InternetMaxBandwidthIn"] = "200"
  550. }
  551. if publicIp.PublicIpChargeType == cloudprovider.ElasticipChargeTypeByBandwidth {
  552. params["InternetChargeType"] = "PayByBandwidth"
  553. }
  554. if len(passwd) > 0 {
  555. params["Password"] = passwd
  556. } else {
  557. params["PasswordInherit"] = "True"
  558. }
  559. if len(projectId) > 0 {
  560. params["ResourceGroupId"] = projectId
  561. }
  562. //{"Code":"InvalidSystemDiskCategory.ValueNotSupported","HostId":"ecs.aliyuncs.com","Message":"The specified parameter 'SystemDisk.Category' is not support IoOptimized Instance. Valid Values: cloud_efficiency;cloud_ssd. ","RequestId":"9C9A4E99-5196-42A2-80B6-4762F8F75C90"}
  563. params["IoOptimized"] = "optimized"
  564. for i, d := range disks {
  565. if i == 0 {
  566. params["SystemDisk.Category"] = d.Category
  567. if d.Category == api.STORAGE_CLOUD_ESSD_PL0 {
  568. params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD
  569. params["SystemDisk.PerformanceLevel"] = "PL0"
  570. }
  571. if d.Category == api.STORAGE_CLOUD_ESSD_PL2 {
  572. params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD
  573. params["SystemDisk.PerformanceLevel"] = "PL2"
  574. }
  575. if d.Category == api.STORAGE_CLOUD_ESSD_PL3 {
  576. params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD
  577. params["SystemDisk.PerformanceLevel"] = "PL3"
  578. }
  579. if d.Category == api.STORAGE_CLOUD_AUTO {
  580. params["SystemDisk.BurstingEnabled"] = "true"
  581. }
  582. params["SystemDisk.Size"] = fmt.Sprintf("%d", d.Size)
  583. params["SystemDisk.DiskName"] = d.GetName()
  584. params["SystemDisk.Description"] = d.Description
  585. } else {
  586. params[fmt.Sprintf("DataDisk.%d.Size", i)] = fmt.Sprintf("%d", d.Size)
  587. params[fmt.Sprintf("DataDisk.%d.Category", i)] = d.Category
  588. if d.Category == api.STORAGE_CLOUD_ESSD_PL0 {
  589. params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD
  590. params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL0"
  591. }
  592. if d.Category == api.STORAGE_CLOUD_ESSD_PL2 {
  593. params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD
  594. params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL2"
  595. }
  596. if d.Category == api.STORAGE_CLOUD_ESSD_PL3 {
  597. params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD
  598. params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL3"
  599. }
  600. if d.Category == api.STORAGE_CLOUD_AUTO {
  601. params[fmt.Sprintf("DataDisk.%d.BurstingEnabled", i)] = "true"
  602. }
  603. params[fmt.Sprintf("DataDisk.%d.DiskName", i)] = d.GetName()
  604. params[fmt.Sprintf("DataDisk.%d.Description", i)] = d.Description
  605. params[fmt.Sprintf("DataDisk.%d.Encrypted", i)] = "false"
  606. }
  607. }
  608. params["VSwitchId"] = vSwitchId
  609. params["PrivateIpAddress"] = ipAddr
  610. if len(keypair) > 0 {
  611. params["KeyPairName"] = keypair
  612. }
  613. if len(userData) > 0 {
  614. params["UserData"] = userData
  615. }
  616. if len(tags) > 0 {
  617. tagIdx := 1
  618. for k, v := range tags {
  619. params[fmt.Sprintf("Tag.%d.Key", tagIdx)] = k
  620. params[fmt.Sprintf("Tag.%d.Value", tagIdx)] = v
  621. tagIdx += 1
  622. }
  623. }
  624. if bc != nil {
  625. params["InstanceChargeType"] = "PrePaid"
  626. err := billingCycle2Params(bc, params)
  627. if err != nil {
  628. return "", err
  629. }
  630. if bc.AutoRenew {
  631. params["AutoRenew"] = "true"
  632. params["AutoRenewPeriod"] = "1"
  633. } else {
  634. params["AutoRenew"] = "False"
  635. }
  636. } else {
  637. params["InstanceChargeType"] = "PostPaid"
  638. params["SpotStrategy"] = "NoSpot"
  639. }
  640. params["ClientToken"] = utils.GenRequestId(20)
  641. resp, err := self.ecsRequest("RunInstances", params)
  642. if err != nil {
  643. return "", errors.Wrapf(err, "RunInstances")
  644. }
  645. ids := []string{}
  646. err = resp.Unmarshal(&ids, "InstanceIdSets", "InstanceIdSet")
  647. if err != nil {
  648. return "", errors.Wrapf(err, "Unmarshal")
  649. }
  650. for _, id := range ids {
  651. err = cloudprovider.Wait(time.Second*3, time.Minute, func() (bool, error) {
  652. _, err := self.GetInstance(id)
  653. if err != nil {
  654. if errors.Cause(err) == cloudprovider.ErrNotFound {
  655. return false, nil
  656. }
  657. return false, err
  658. }
  659. return true, nil
  660. })
  661. if err != nil {
  662. return "", errors.Wrapf(cloudprovider.ErrNotFound, "after vm %s created", id)
  663. }
  664. return id, nil
  665. }
  666. return "", errors.Wrapf(cloudprovider.ErrNotFound, "after created")
  667. }
  668. func (self *SRegion) AllocatePublicIpAddress(instanceId string) (string, error) {
  669. params := map[string]string{
  670. "InstanceId": instanceId,
  671. }
  672. resp, err := self.ecsRequest("AllocatePublicIpAddress", params)
  673. if err != nil {
  674. return "", errors.Wrapf(err, "AllocatePublicIpAddress")
  675. }
  676. return resp.GetString("IpAddress")
  677. }
  678. func (self *SInstance) AllocatePublicIpAddress() (string, error) {
  679. return self.host.zone.region.AllocatePublicIpAddress(self.InstanceId)
  680. }
  681. func (self *SRegion) doStartVM(instanceId string) error {
  682. return self.instanceOperation(instanceId, "StartInstance", nil)
  683. }
  684. func (self *SRegion) doStopVM(instanceId string, isForce, stopCharging bool) error {
  685. params := make(map[string]string)
  686. if isForce {
  687. params["ForceStop"] = "true"
  688. } else {
  689. params["ForceStop"] = "false"
  690. }
  691. params["StoppedMode"] = "KeepCharging"
  692. if stopCharging {
  693. params["StoppedMode"] = "StopCharging"
  694. }
  695. return self.instanceOperation(instanceId, "StopInstance", params)
  696. }
  697. func (self *SRegion) doDeleteVM(instanceId string) error {
  698. params := make(map[string]string)
  699. params["TerminateSubscription"] = "true" // terminate expired prepaid instance
  700. params["Force"] = "true"
  701. return self.instanceOperation(instanceId, "DeleteInstance", params)
  702. }
  703. func (self *SRegion) StartVM(instanceId string) error {
  704. status, err := self.GetInstanceStatus(instanceId)
  705. if err != nil {
  706. return errors.Wrapf(err, "GetInstanceStatus")
  707. }
  708. if status != InstanceStatusStopped {
  709. return errors.Wrapf(cloudprovider.ErrInvalidStatus, "vm status is %s expect %s", status, InstanceStatusStopped)
  710. }
  711. return self.doStartVM(instanceId)
  712. }
  713. func (self *SRegion) StopVM(instanceId string, isForce, stopCharging bool) error {
  714. status, err := self.GetInstanceStatus(instanceId)
  715. if err != nil {
  716. return errors.Wrapf(err, "GetInstanceStatus")
  717. }
  718. if status == InstanceStatusStopped {
  719. return nil
  720. }
  721. if status != InstanceStatusRunning {
  722. return errors.Wrapf(cloudprovider.ErrInvalidStatus, "vm status is %s expect %s", status, InstanceStatusRunning)
  723. }
  724. return self.doStopVM(instanceId, isForce, stopCharging)
  725. }
  726. func (self *SRegion) DeleteVM(instanceId string) error {
  727. status, err := self.GetInstanceStatus(instanceId)
  728. if err != nil {
  729. return errors.Wrapf(err, "GetInstanceStatus")
  730. }
  731. if status != InstanceStatusStopped {
  732. log.Warningf("DeleteVM: vm status is %s expect %s", status, InstanceStatusStopped)
  733. }
  734. return self.doDeleteVM(instanceId)
  735. }
  736. func (self *SRegion) DeployVM(instanceId string, opts *cloudprovider.SInstanceDeployOptions) error {
  737. instance, err := self.GetInstance(instanceId)
  738. if err != nil {
  739. return errors.Wrapf(err, "GetInstance")
  740. }
  741. // 修改密钥时直接返回
  742. if opts.DeleteKeypair {
  743. err = self.DetachKeyPair(instanceId, instance.KeyPairName)
  744. if err != nil {
  745. return errors.Wrapf(err, "DetachKeyPair")
  746. }
  747. }
  748. var keypairName string
  749. if len(opts.PublicKey) > 0 {
  750. var err error
  751. keypairName, err = self.syncKeypair(opts.PublicKey)
  752. if err != nil {
  753. return errors.Wrapf(err, "syncKeypair")
  754. }
  755. err = self.AttachKeypair(instanceId, keypairName)
  756. if err != nil {
  757. return errors.Wrapf(err, "AttachKeypair")
  758. }
  759. }
  760. // 指定密码的情况下,使用指定的密码
  761. if len(opts.Password) > 0 {
  762. params := make(map[string]string)
  763. params["Password"] = opts.Password
  764. return self.modifyInstanceAttribute(instanceId, params)
  765. }
  766. return nil
  767. }
  768. func (self *SInstance) DeleteVM(ctx context.Context) error {
  769. // 未到期包年包月实例需要先转换到按量计费后, 进行删除
  770. if self.GetBillingType() == billing_api.BILLING_TYPE_PREPAID && self.GetExpiredAt().After(time.Now()) {
  771. err := self.host.zone.region.ConvertVmPostpaid([]string{self.InstanceId})
  772. if err != nil {
  773. log.Warningf("convert vm %s to postpaid error: %v", self.InstanceId, err)
  774. }
  775. }
  776. for {
  777. err := self.host.zone.region.DeleteVM(self.InstanceId)
  778. if err == nil {
  779. break
  780. }
  781. e, ok := errors.Cause(err).(*alierr.ServerError)
  782. if !ok {
  783. return errors.Wrapf(err, "DeleteVM")
  784. }
  785. switch e.ErrorCode() {
  786. case "IncorrectInstanceStatus.Initializing":
  787. time.Sleep(10 * time.Second)
  788. case "LastTokenProcessing": // 等待转换按量付费完成
  789. time.Sleep(10 * time.Second)
  790. default:
  791. return errors.Wrapf(err, "DeleteVM")
  792. }
  793. }
  794. return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes
  795. }
  796. func (self *SRegion) ConvertVmPostpaid(instanceIds []string) error {
  797. params := map[string]string{
  798. "RegionId": self.RegionId,
  799. "InstanceIds": jsonutils.Marshal(instanceIds).String(),
  800. "InstanceChargeType": "PostPaid",
  801. "ClientToken": utils.GenRequestId(20),
  802. }
  803. _, err := self.ecsRequest("ModifyInstanceChargeType", params)
  804. return err
  805. }
  806. func (self *SRegion) UpdateVM(instanceId string, input cloudprovider.SInstanceUpdateOptions, osType string) error {
  807. /*
  808. api: ModifyInstanceAttribute
  809. https://help.aliyun.com/document_detail/25503.html?spm=a2c4g.11186623.4.1.DrgpjW
  810. */
  811. params := make(map[string]string)
  812. params["InstanceName"] = input.NAME
  813. if len(input.HostName) > 0 {
  814. params["HostName"] = input.HostName
  815. }
  816. if len(input.Description) > 0 {
  817. params["Description"] = input.Description
  818. }
  819. return self.modifyInstanceAttribute(instanceId, params)
  820. }
  821. func (self *SRegion) modifyInstanceAttribute(instanceId string, params map[string]string) error {
  822. return self.instanceOperation(instanceId, "ModifyInstanceAttribute", params)
  823. }
  824. func (self *SRegion) ReplaceSystemDisk(instanceId string, imageId string, passwd string, keypairName string, sysDiskSizeGB int) (string, error) {
  825. params := make(map[string]string)
  826. params["RegionId"] = self.RegionId
  827. params["InstanceId"] = instanceId
  828. params["ImageId"] = imageId
  829. if len(passwd) > 0 {
  830. params["Password"] = passwd
  831. } else {
  832. params["PasswordInherit"] = "True"
  833. }
  834. if len(keypairName) > 0 {
  835. params["KeyPairName"] = keypairName
  836. }
  837. if sysDiskSizeGB > 0 {
  838. params["SystemDisk.Size"] = fmt.Sprintf("%d", sysDiskSizeGB)
  839. }
  840. body, err := self.ecsRequest("ReplaceSystemDisk", params)
  841. if err != nil {
  842. return "", errors.Wrapf(err, "ReplaceSystemDisk")
  843. }
  844. return body.GetString("DiskId")
  845. }
  846. func (self *SRegion) ChangePrepaidVMConfig(instanceId string, instanceType string, isDowngrade bool) error {
  847. // todo: support change disk config?
  848. params := make(map[string]string)
  849. params["InstanceType"] = instanceType
  850. params["ClientToken"] = utils.GenRequestId(20)
  851. if isDowngrade {
  852. params["OperatorType"] = "downgrade"
  853. }
  854. err := self.instanceOperation(instanceId, "ModifyPrepayInstanceSpec", params)
  855. if err != nil {
  856. return errors.Wrapf(err, "ModifyPrepayInstanceSpec %s", instanceType)
  857. }
  858. return nil
  859. }
  860. func (self *SRegion) ChangeVMConfig(instanceId string, instanceType string) error {
  861. // todo: support change disk config?
  862. params := make(map[string]string)
  863. params["InstanceType"] = instanceType
  864. params["ClientToken"] = utils.GenRequestId(20)
  865. err := self.instanceOperation(instanceId, "ModifyInstanceSpec", params)
  866. if err != nil {
  867. return errors.Wrapf(err, "ModifyInstanceSpec %s", instanceType)
  868. }
  869. return nil
  870. }
  871. func (self *SRegion) DetachDisk(instanceId string, diskId string) error {
  872. params := make(map[string]string)
  873. params["InstanceId"] = instanceId
  874. params["DiskId"] = diskId
  875. log.Infof("Detach instance %s disk %s", instanceId, diskId)
  876. _, err := self.ecsRequest("DetachDisk", params)
  877. if err != nil {
  878. if strings.Contains(err.Error(), "The specified disk has not been attached on the specified instance") {
  879. return nil
  880. }
  881. return errors.Wrap(err, "DetachDisk")
  882. }
  883. return nil
  884. }
  885. func (self *SRegion) AttachDisk(instanceId string, diskId string) error {
  886. params := make(map[string]string)
  887. params["InstanceId"] = instanceId
  888. params["DiskId"] = diskId
  889. params["DeleteWithInstance"] = "true"
  890. _, err := self.ecsRequest("AttachDisk", params)
  891. if err != nil {
  892. return errors.Wrapf(err, "AttachDisk %s => %s", diskId, instanceId)
  893. }
  894. return nil
  895. }
  896. func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
  897. if len(self.EipAddress.IpAddress) > 0 {
  898. return self.host.zone.region.GetEip(self.EipAddress.AllocationId)
  899. }
  900. if len(self.PublicIpAddress.IpAddress) > 0 {
  901. eip := SEipAddress{}
  902. eip.region = self.host.zone.region
  903. eip.IpAddress = self.PublicIpAddress.IpAddress[0]
  904. eip.InstanceId = self.InstanceId
  905. eip.InstanceType = EIP_INSTANCE_TYPE_ECS
  906. eip.Status = EIP_STATUS_INUSE
  907. eip.AllocationId = self.InstanceId // fixed
  908. eip.AllocationTime = self.CreationTime
  909. eip.Bandwidth = self.InternetMaxBandwidthOut
  910. eip.ResourceGroupId = self.ResourceGroupId
  911. eip.InternetChargeType = self.InternetChargeType
  912. return &eip, nil
  913. }
  914. return nil, nil
  915. }
  916. func (self *SInstance) SetSecurityGroups(secgroupIds []string) error {
  917. return self.host.zone.region.SetSecurityGroups(secgroupIds, self.InstanceId)
  918. }
  919. func (self *SInstance) GetBillingType() string {
  920. return convertChargeType(self.InstanceChargeType)
  921. }
  922. func (self *SInstance) ChangeBillingType(billingType string) error {
  923. return self.host.zone.region.ModifyInstanceChargeType(self.InstanceId, billingType)
  924. }
  925. func (region *SRegion) ModifyInstanceChargeType(vmId string, billingType string) error {
  926. params := map[string]string{
  927. "RegionId": region.RegionId,
  928. "IncludeDataDisks": "true",
  929. "AutoPay": "true",
  930. "ClientToken": utils.GenRequestId(20),
  931. "InstanceIds": jsonutils.Marshal([]string{vmId}).String(),
  932. }
  933. switch billingType {
  934. case billing_api.BILLING_TYPE_POSTPAID:
  935. params["InstanceChargeType"] = "PostPaid"
  936. case billing_api.BILLING_TYPE_PREPAID:
  937. params["InstanceChargeType"] = "PrePaid"
  938. params["Period"] = "1"
  939. params["PeriodUnit"] = "Month"
  940. default:
  941. return fmt.Errorf("invalid billing_type %s", billingType)
  942. }
  943. _, err := region.ecsRequest("ModifyInstanceChargeType", params)
  944. if err != nil {
  945. return errors.Wrapf(err, "ModifyInstanceChargeType %v", params)
  946. }
  947. if billingType == billing_api.BILLING_TYPE_PREPAID {
  948. cycle := billing.SBillingCycle{
  949. AutoRenew: true,
  950. Count: 1,
  951. Unit: billing.BillingCycleMonth,
  952. }
  953. err = cloudprovider.Wait(time.Second*10, time.Minute*3, func() (bool, error) {
  954. err = region.SetInstanceAutoRenew(vmId, cycle)
  955. if err != nil {
  956. log.Errorf("set auto renew for instance %s error: %v", vmId, err)
  957. return false, nil
  958. }
  959. return true, nil
  960. })
  961. if err != nil {
  962. log.Errorf("set auto renew for %s error: %v", vmId, err)
  963. }
  964. }
  965. return nil
  966. }
  967. func (self *SInstance) GetCreatedAt() time.Time {
  968. return self.CreationTime
  969. }
  970. func (self *SInstance) GetExpiredAt() time.Time {
  971. return convertExpiredAt(self.ExpiredTime)
  972. }
  973. func (self *SInstance) UpdateUserData(userData string) error {
  974. return self.host.zone.region.updateInstance(self.InstanceId, "", "", "", "", userData)
  975. }
  976. func (self *SInstance) Renew(bc billing.SBillingCycle) error {
  977. return self.host.zone.region.RenewInstance(self.InstanceId, bc)
  978. }
  979. func (self *SInstance) GetThroughput() int {
  980. return self.Throughput
  981. }
  982. func (self *SInstance) GetInternetMaxBandwidthOut() int {
  983. return self.InternetMaxBandwidthOut
  984. }
  985. func billingCycle2Params(bc *billing.SBillingCycle, params map[string]string) error {
  986. if bc.GetMonths() > 0 {
  987. params["PeriodUnit"] = "Month"
  988. params["Period"] = fmt.Sprintf("%d", bc.GetMonths())
  989. } else if bc.GetWeeks() > 0 {
  990. params["PeriodUnit"] = "Week"
  991. params["Period"] = fmt.Sprintf("%d", bc.GetWeeks())
  992. } else {
  993. return fmt.Errorf("invalid renew time period %s", bc.String())
  994. }
  995. return nil
  996. }
  997. func (region *SRegion) RenewInstance(instanceId string, bc billing.SBillingCycle) error {
  998. params := make(map[string]string)
  999. params["InstanceId"] = instanceId
  1000. err := billingCycle2Params(&bc, params)
  1001. if err != nil {
  1002. return errors.Wrapf(err, "billingCycle2Params %v", params)
  1003. }
  1004. params["ClientToken"] = utils.GenRequestId(20)
  1005. _, err = region.ecsRequest("RenewInstance", params)
  1006. if err != nil {
  1007. return errors.Wrapf(err, "RenewInstance %v", params)
  1008. }
  1009. return nil
  1010. }
  1011. func (self *SInstance) GetProjectId() string {
  1012. return self.ResourceGroupId
  1013. }
  1014. func (self *SInstance) GetError() error {
  1015. return nil
  1016. }
  1017. func (region *SRegion) ConvertPublicIpToEip(instanceId string) error {
  1018. params := make(map[string]string)
  1019. params["InstanceId"] = instanceId
  1020. params["RegionId"] = region.RegionId
  1021. _, err := region.ecsRequest("ConvertNatPublicIpToEip", params)
  1022. return err
  1023. }
  1024. func (self *SInstance) ConvertPublicIpToEip() error {
  1025. err := self.host.zone.region.ConvertPublicIpToEip(self.InstanceId)
  1026. if err != nil {
  1027. return errors.Wrapf(err, "ConvertPublicIpToEip")
  1028. }
  1029. return cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) {
  1030. self.Refresh()
  1031. iEip, err := self.GetIEIP()
  1032. if err != nil {
  1033. return false, errors.Wrapf(err, "GetIEIP")
  1034. }
  1035. if iEip == nil {
  1036. return false, nil
  1037. }
  1038. if iEip.GetMode() == api.EIP_MODE_STANDALONE_EIP {
  1039. return true, self.host.zone.region.VpcMoveResourceGroup("eip", self.ResourceGroupId, iEip.GetId())
  1040. }
  1041. return false, nil
  1042. })
  1043. }
  1044. func (self *SRegion) VpcMoveResourceGroup(resType, groupId, resId string) error {
  1045. params := map[string]string{
  1046. "RegionId": self.RegionId,
  1047. "ResourceType": resType,
  1048. "NewResourceGroupId": groupId,
  1049. "ResourceId": resId,
  1050. }
  1051. _, err := self.vpcRequest("MoveResourceGroup", params)
  1052. return errors.Wrapf(err, "MoveResourceGroup")
  1053. }
  1054. // https://help.aliyun.com/document_detail/52843.html
  1055. func (region *SRegion) SetInstanceAutoRenew(instanceId string, bc billing.SBillingCycle) error {
  1056. params := make(map[string]string)
  1057. params["InstanceId"] = instanceId
  1058. params["RegionId"] = region.RegionId
  1059. if bc.AutoRenew {
  1060. params["RenewalStatus"] = "AutoRenewal"
  1061. switch bc.Unit {
  1062. case billing.BillingCycleYear:
  1063. params["Duration"] = fmt.Sprintf("%d", bc.GetYears())
  1064. params["PeriodUnit"] = "Year"
  1065. case billing.BillingCycleMonth:
  1066. params["Duration"] = fmt.Sprintf("%d", bc.GetMonths())
  1067. params["PeriodUnit"] = "Month"
  1068. case billing.BillingCycleWeek:
  1069. params["Duration"] = fmt.Sprintf("%d", bc.GetWeeks())
  1070. params["PeriodUnit"] = "Week"
  1071. }
  1072. } else {
  1073. params["RenewalStatus"] = "Normal"
  1074. }
  1075. _, err := region.ecsRequest("ModifyInstanceAutoRenewAttribute", params)
  1076. return err
  1077. }
  1078. type SAutoRenewAttr struct {
  1079. Duration int
  1080. AutoRenewEnabled bool
  1081. RenewalStatus string
  1082. PeriodUnit string
  1083. }
  1084. func (region *SRegion) GetInstanceAutoRenewAttribute(instanceId string) (*SAutoRenewAttr, error) {
  1085. params := make(map[string]string)
  1086. params["InstanceId"] = instanceId
  1087. params["RegionId"] = region.RegionId
  1088. resp, err := region.ecsRequest("DescribeInstanceAutoRenewAttribute", params)
  1089. if err != nil {
  1090. return nil, errors.Wrap(err, "DescribeInstanceAutoRenewAttribute")
  1091. }
  1092. attr := []SAutoRenewAttr{}
  1093. err = resp.Unmarshal(&attr, "InstanceRenewAttributes", "InstanceRenewAttribute")
  1094. if err != nil {
  1095. return nil, errors.Wrap(err, "resp.Unmarshal")
  1096. }
  1097. if len(attr) == 1 {
  1098. return &attr[0], nil
  1099. }
  1100. return nil, fmt.Errorf("get %d auto renew info", len(attr))
  1101. }
  1102. func (self *SInstance) IsAutoRenew() bool {
  1103. attr, err := self.host.zone.region.GetInstanceAutoRenewAttribute(self.InstanceId)
  1104. if err != nil {
  1105. log.Errorf("failed to get instance %s auto renew info", self.InstanceId)
  1106. return false
  1107. }
  1108. return attr.AutoRenewEnabled
  1109. }
  1110. func (self *SInstance) SetAutoRenew(bc billing.SBillingCycle) error {
  1111. return self.host.zone.region.SetInstanceAutoRenew(self.InstanceId, bc)
  1112. }
  1113. func (self *SInstance) SetTags(tags map[string]string, replace bool) error {
  1114. return self.host.zone.region.SetResourceTags(ALIYUN_SERVICE_ECS, "instance", self.InstanceId, tags, replace)
  1115. }
  1116. func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
  1117. params := map[string]string{
  1118. "InstanceId": instanceId,
  1119. "ImageName": opts.Name,
  1120. "Description": opts.Notes,
  1121. "ClientToken": utils.GenRequestId(20),
  1122. }
  1123. resp, err := self.ecsRequest("CreateImage", params)
  1124. if err != nil {
  1125. return nil, errors.Wrapf(err, "CreateImage")
  1126. }
  1127. ret := struct{ ImageId string }{}
  1128. err = resp.Unmarshal(&ret)
  1129. if err != nil {
  1130. return nil, errors.Wrapf(err, "resp.Unmarshal")
  1131. }
  1132. image, err := self.GetImage(ret.ImageId)
  1133. if err != nil {
  1134. return nil, errors.Wrapf(err, "GetImage(%s)", ret.ImageId)
  1135. }
  1136. image.storageCache = self.getStoragecache()
  1137. return image, nil
  1138. }
  1139. func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
  1140. image, err := self.host.zone.region.SaveImage(self.InstanceId, opts)
  1141. if err != nil {
  1142. return nil, errors.Wrapf(err, "SaveImage(%s)", opts.Name)
  1143. }
  1144. return image, nil
  1145. }