instance.go 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106
  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 qcloud
  15. import (
  16. "context"
  17. "fmt"
  18. "strconv"
  19. "strings"
  20. "time"
  21. sdkerrors "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/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/utils"
  28. billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
  29. api "yunion.io/x/cloudmux/pkg/apis/compute"
  30. "yunion.io/x/cloudmux/pkg/cloudprovider"
  31. "yunion.io/x/cloudmux/pkg/multicloud"
  32. )
  33. const (
  34. // PENDING:表示创建中
  35. // LAUNCH_FAILED:表示创建失败
  36. // RUNNING:表示运行中
  37. // STOPPED:表示关机
  38. // STARTING:表示开机中
  39. // STOPPING:表示关机中
  40. // REBOOTING:表示重启中
  41. // SHUTDOWN:表示停止待销毁
  42. // TERMINATING:表示销毁中。
  43. InstanceStatusStopped = "STOPPED"
  44. InstanceStatusRunning = "RUNNING"
  45. InstanceStatusStopping = "STOPPING"
  46. InstanceStatusStarting = "STARTING"
  47. )
  48. const (
  49. InternetChargeTypeBandwidthPrepaid = "BANDWIDTH_PREPAID"
  50. InternetChargeTypeTrafficPostpaidByHour = "TRAFFIC_POSTPAID_BY_HOUR"
  51. InternetChargeTypeBandwidthPostpaidByHour = "BANDWIDTH_POSTPAID_BY_HOUR"
  52. InternetChargeTypeBandwidthPackage = "BANDWIDTH_PACKAGE"
  53. )
  54. type SystemDisk struct {
  55. DiskType string //系统盘类型。系统盘类型限制详见CVM实例配置。取值范围:LOCAL_BASIC:本地硬盘 LOCAL_SSD:本地SSD硬盘 CLOUD_BASIC:普通云硬盘 CLOUD_SSD:SSD云硬盘 CLOUD_PREMIUM:高性能云硬盘 默认取值:CLOUD_BASIC。
  56. DiskId string // 系统盘ID。LOCAL_BASIC 和 LOCAL_SSD 类型没有ID。暂时不支持该参数。
  57. DiskSize float32 //系统盘大小,单位:GB。默认值为 50
  58. }
  59. type DataDisk struct {
  60. DiskSize float32 // 数据盘大小,单位:GB。最小调整步长为10G,不同数据盘类型取值范围不同,具体限制详见:CVM实例配置。默认值为0,表示不购买数据盘。更多限制详见产品文档。
  61. DiskType string // 数据盘类型。数据盘类型限制详见CVM实例配置。取值范围:LOCAL_BASIC:本地硬盘 LOCAL_SSD:本地SSD硬盘 CLOUD_BASIC:普通云硬盘 CLOUD_PREMIUM:高性能云硬盘 CLOUD_SSD:SSD云硬盘 默认取值:LOCAL_BASIC。 该参数对ResizeInstanceDisk接口无效。
  62. DiskId string // 数据盘ID。LOCAL_BASIC 和 LOCAL_SSD 类型没有ID。暂时不支持该参数。
  63. DeleteWithInstance bool // 数据盘是否随子机销毁。取值范围:TRUE:子机销毁时,销毁数据盘 FALSE:子机销毁时,保留数据盘 默认取值:TRUE 该参数目前仅用于 RunInstances 接口。
  64. }
  65. type InternetAccessible struct {
  66. InternetChargeType string //网络计费类型。取值范围:BANDWIDTH_PREPAID:预付费按带宽结算 TRAFFIC_POSTPAID_BY_HOUR:流量按小时后付费 BANDWIDTH_POSTPAID_BY_HOUR:带宽按小时后付费 BANDWIDTH_PACKAGE:带宽包用户 默认取值:非带宽包用户默认与子机付费类型保持一致。
  67. InternetMaxBandwidthOut int // 公网出带宽上限,单位:Mbps。默认值:0Mbps。不同机型带宽上限范围不一致,具体限制详见购买网络带宽。
  68. PublicIpAssigned bool // 是否分配公网IP。取值范围: TRUE:表示分配公网IP FALSE:表示不分配公网IP 当公网带宽大于0Mbps时,可自由选择开通与否,默认开通公网IP;当公网带宽为0,则不允许分配公网IP。
  69. }
  70. type VirtualPrivateCloud struct {
  71. VpcId string // 私有网络ID,形如vpc-xxx。有效的VpcId可通过登录控制台查询;也可以调用接口 DescribeVpcEx ,从接口返回中的unVpcId字段获取。
  72. SubnetId string // 私有网络子网ID,形如subnet-xxx。有效的私有网络子网ID可通过登录控制台查询;也可以调用接口 DescribeSubnets ,从接口返回中的unSubnetId字段获取。
  73. AsVpcGateway bool // 是否用作公网网关。公网网关只有在实例拥有公网IP以及处于私有网络下时才能正常使用。取值范围:TRUE:表示用作公网网关 FALSE:表示不用作公网网关 默认取值:FALSE。
  74. PrivateIpAddresses []string // 私有网络子网 IP 数组,在创建实例、修改实例vpc属性操作中可使用此参数。当前仅批量创建多台实例时支持传入相同子网的多个 IP。
  75. }
  76. type LoginSettings struct {
  77. Password string //实例登录密码。不同操作系统类型密码复杂度限制不一样,具体如下:Linux实例密码必须8到16位,至少包括两项[a-z,A-Z]、[0-9] 和 [( ) ~ ! @ # $ % ^ & * - + = &#124; { } [ ] : ; ' , . ? / ]中的特殊符号。<br><li>Windows实例密码必须12到16位,至少包括三项[a-z],[A-Z],[0-9] 和 [( ) ~ ! @ # $ % ^ & * - + = { } [ ] : ; ' , . ? /]中的特殊符号。 若不指定该参数,则由系统随机生成密码,并通过站内信方式通知到用户。
  78. KeyIds []string // 密钥ID列表。关联密钥后,就可以通过对应的私钥来访问实例;KeyId可通过接口DescribeKeyPairs获取,密钥与密码不能同时指定,同时Windows操作系统不支持指定密钥。当前仅支持购买的时候指定一个密钥。
  79. KeepImageLogin string // 保持镜像的原始设置。该参数与Password或KeyIds.N不能同时指定。只有使用自定义镜像、共享镜像或外部导入镜像创建实例时才能指定该参数为TRUE。取值范围: TRUE:表示保持镜像的登录设置 FALSE:表示不保持镜像的登录设置 默认取值:FALSE。
  80. }
  81. type Tag struct {
  82. Key string
  83. Value string
  84. }
  85. type SInstance struct {
  86. multicloud.SInstanceBase
  87. QcloudTags
  88. host *SHost
  89. // normalized image info
  90. osInfo *imagetools.ImageInfo
  91. image *SImage
  92. idisks []cloudprovider.ICloudDisk
  93. Placement Placement
  94. InstanceId string
  95. InstanceType string
  96. CPU int
  97. Memory int
  98. RestrictState string //NORMAL EXPIRED PROTECTIVELY_ISOLATED
  99. InstanceName string
  100. InstanceChargeType InstanceChargeType //PREPAID:表示预付费,即包年包月 POSTPAID_BY_HOUR:表示后付费,即按量计费 CDHPAID:CDH付费,即只对CDH计费,不对CDH上的实例计费。
  101. SystemDisk SystemDisk //实例系统盘信息。
  102. DataDisks []DataDisk //实例数据盘信息。只包含随实例购买的数据盘。
  103. PrivateIpAddresses []string //实例主网卡的内网IP列表。
  104. PublicIpAddresses []string //实例主网卡的公网IP列表。
  105. InternetAccessible InternetAccessible //实例带宽信息。
  106. VirtualPrivateCloud VirtualPrivateCloud //实例所属虚拟私有网络信息。
  107. ImageId string // 生产实例所使用的镜像ID。
  108. RenewFlag string // 自动续费标识。取值范围:NOTIFY_AND_MANUAL_RENEW:表示通知即将过期,但不自动续费 NOTIFY_AND_AUTO_RENEW:表示通知即将过期,而且自动续费 DISABLE_NOTIFY_AND_MANUAL_RENEW:表示不通知即将过期,也不自动续费。
  109. CreatedTime time.Time // 创建时间。按照ISO8601标准表示,并且使用UTC时间。格式为:YYYY-MM-DDThh:mm:ssZ。
  110. ExpiredTime time.Time // 到期时间。按照ISO8601标准表示,并且使用UTC时间。格式为:YYYY-MM-DDThh:mm:ssZ。
  111. OsName string // 操作系统名称。
  112. SecurityGroupIds []string // 实例所属安全组。该参数可以通过调用 DescribeSecurityGroups 的返回值中的sgId字段来获取。
  113. LoginSettings LoginSettings //实例登录设置。目前只返回实例所关联的密钥。
  114. InstanceState string // 实例状态。取值范围:PENDING:表示创建中 LAUNCH_FAILED:表示创建失败 RUNNING:表示运行中 STOPPED:表示关机 STARTING:表示开机中 STOPPING:表示关机中 REBOOTING:表示重启中 SHUTDOWN:表示停止待销毁 TERMINATING:表示销毁中。
  115. IPv6Addresses []string
  116. Tags []Tag
  117. }
  118. func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit int) ([]SInstance, int, error) {
  119. params := make(map[string]string)
  120. if limit < 1 || limit > 50 {
  121. limit = 50
  122. }
  123. params["Limit"] = fmt.Sprintf("%d", limit)
  124. params["Offset"] = fmt.Sprintf("%d", offset)
  125. instances := make([]SInstance, 0)
  126. if ids != nil && len(ids) > 0 {
  127. for index, id := range ids {
  128. params[fmt.Sprintf("InstanceIds.%d", index)] = id
  129. }
  130. } else {
  131. if len(zoneId) > 0 {
  132. params["Filters.0.Name"] = "zone"
  133. params["Filters.0.Values.0"] = zoneId
  134. }
  135. }
  136. body, err := self.cvmRequest("DescribeInstances", params, true)
  137. if err != nil {
  138. return nil, 0, err
  139. }
  140. err = body.Unmarshal(&instances, "InstanceSet")
  141. if err != nil {
  142. return nil, 0, err
  143. }
  144. total, _ := body.Float("TotalCount")
  145. return instances, int(total), nil
  146. }
  147. func (self *SInstance) GetSecurityGroupIds() ([]string, error) {
  148. return self.SecurityGroupIds, nil
  149. }
  150. func (self *SInstance) getCloudMetadata() (map[string]string, error) {
  151. mtags, err := self.host.zone.region.FetchResourceTags("cvm", "instance", []string{self.InstanceId})
  152. if err != nil {
  153. return nil, errors.Wrap(err, "FetchResourceTags")
  154. }
  155. if tags, ok := mtags[self.InstanceId]; ok {
  156. return *tags, nil
  157. } else {
  158. return nil, nil
  159. }
  160. }
  161. func (self *SInstance) GetIHost() cloudprovider.ICloudHost {
  162. return self.host
  163. }
  164. func (self *SInstance) GetId() string {
  165. return self.InstanceId
  166. }
  167. func (self *SInstance) GetName() string {
  168. if len(self.InstanceName) > 0 && self.InstanceName != "未命名" {
  169. return self.InstanceName
  170. }
  171. return self.InstanceId
  172. }
  173. func (self *SInstance) GetHostname() string {
  174. return ""
  175. }
  176. func (self *SInstance) GetGlobalId() string {
  177. return self.InstanceId
  178. }
  179. func (self *SInstance) IsEmulated() bool {
  180. return false
  181. }
  182. func (self *SInstance) GetInstanceType() string {
  183. return self.InstanceType
  184. }
  185. func (self *SInstance) getVpc() (*SVpc, error) {
  186. return self.host.zone.region.GetVpc(self.VirtualPrivateCloud.VpcId)
  187. }
  188. func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
  189. idisks := make([]cloudprovider.ICloudDisk, 0)
  190. if utils.IsInStringArray(strings.ToUpper(self.SystemDisk.DiskType), QCLOUD_LOCAL_STORAGE_TYPES) {
  191. storage := SLocalStorage{zone: self.host.zone, storageType: self.SystemDisk.DiskType}
  192. disk := SLocalDisk{
  193. storage: &storage,
  194. DiskId: self.SystemDisk.DiskId,
  195. DiskSize: self.SystemDisk.DiskSize,
  196. DisktType: self.SystemDisk.DiskType,
  197. DiskUsage: "SYSTEM_DISK",
  198. imageId: self.ImageId,
  199. }
  200. idisks = append(idisks, &disk)
  201. }
  202. for i := 0; i < len(self.DataDisks); i++ {
  203. if utils.IsInStringArray(strings.ToUpper(self.DataDisks[i].DiskType), QCLOUD_LOCAL_STORAGE_TYPES) {
  204. storage := SLocalStorage{zone: self.host.zone, storageType: self.DataDisks[i].DiskType}
  205. disk := SLocalDisk{
  206. storage: &storage,
  207. DiskId: self.DataDisks[i].DiskId,
  208. DiskSize: self.DataDisks[i].DiskSize,
  209. DisktType: self.DataDisks[i].DiskType,
  210. DiskUsage: "DATA_DISK",
  211. }
  212. idisks = append(idisks, &disk)
  213. }
  214. }
  215. disks := make([]SDisk, 0)
  216. totalDisk := -1
  217. for totalDisk < 0 || len(disks) < totalDisk {
  218. parts, total, err := self.host.zone.region.GetDisks(self.InstanceId, "", "", nil, len(disks), 50)
  219. if err != nil {
  220. log.Errorf("fetchDisks fail %s", err)
  221. return nil, err
  222. }
  223. if len(parts) > 0 {
  224. disks = append(disks, parts...)
  225. }
  226. totalDisk = total
  227. }
  228. for i := 0; i < len(disks); i += 1 {
  229. store, err := self.host.zone.getStorageByCategory(disks[i].DiskType)
  230. if err != nil {
  231. return nil, err
  232. }
  233. disks[i].storage = store
  234. idisks = append(idisks, &disks[i])
  235. }
  236. return idisks, nil
  237. }
  238. func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
  239. classic := self.VirtualPrivateCloud.VpcId == ""
  240. region := self.host.zone.region
  241. ret := []cloudprovider.ICloudNic{}
  242. if classic {
  243. for _, ipAddr := range self.PrivateIpAddresses {
  244. nic := SInstanceNic{
  245. instance: self,
  246. ipAddr: ipAddr,
  247. classic: true,
  248. }
  249. ret = append(ret, &nic)
  250. }
  251. }
  252. nics, _, err := region.GetNetworkInterfaces(nil, self.InstanceId, "", 0, 10)
  253. if err != nil {
  254. return nil, errors.Wrapf(err, "GetNetworkInterfaces")
  255. }
  256. for _, networkInterface := range nics {
  257. nic := &SInstanceNic{
  258. instance: self,
  259. id: String(&networkInterface.NetworkInterfaceId),
  260. macAddr: strings.ToLower(networkInterface.MacAddress),
  261. classic: classic,
  262. }
  263. for _, addr := range networkInterface.PrivateIpAddressSet {
  264. if addr.Primary {
  265. nic.ipAddr = addr.PrivateIpAddress
  266. }
  267. }
  268. for _, addr := range networkInterface.Ipv6AddressSet {
  269. if len(addr.Address) > 0 {
  270. nic.ip6Addr = addr.Address
  271. break
  272. }
  273. }
  274. ret = append(ret, nic)
  275. }
  276. return ret, nil
  277. }
  278. func (self *SInstance) GetVcpuCount() int {
  279. return self.CPU
  280. }
  281. func (self *SInstance) GetVmemSizeMB() int {
  282. return self.Memory * 1024
  283. }
  284. func (self *SInstance) GetBootOrder() string {
  285. return "dcn"
  286. }
  287. func (self *SInstance) GetVga() string {
  288. return "std"
  289. }
  290. func (self *SInstance) GetVdi() string {
  291. return "vnc"
  292. }
  293. func (ins *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo {
  294. if ins.osInfo == nil {
  295. osInfo := imagetools.NormalizeImageInfo(ins.OsName, "", "", "", "")
  296. ins.osInfo = &osInfo
  297. }
  298. return ins.osInfo
  299. }
  300. func (ins *SInstance) GetOsType() cloudprovider.TOsType {
  301. return cloudprovider.TOsType(ins.getNormalizedOsInfo().OsType)
  302. }
  303. func (ins *SInstance) GetBios() cloudprovider.TBiosType {
  304. return cloudprovider.ToBiosType(ins.getNormalizedOsInfo().OsBios)
  305. }
  306. func (ins *SInstance) GetFullOsName() string {
  307. return ins.OsName
  308. }
  309. func (ins *SInstance) GetOsLang() string {
  310. return ins.getNormalizedOsInfo().OsLang
  311. }
  312. func (ins *SInstance) GetOsArch() string {
  313. return ins.getNormalizedOsInfo().OsArch
  314. }
  315. func (ins *SInstance) GetOsDist() string {
  316. return ins.getNormalizedOsInfo().OsDistro
  317. }
  318. func (ins *SInstance) GetOsVersion() string {
  319. return ins.getNormalizedOsInfo().OsVersion
  320. }
  321. func (self *SInstance) GetMachine() string {
  322. return "pc"
  323. }
  324. func (self *SInstance) GetStatus() string {
  325. switch self.InstanceState {
  326. case "PENDING":
  327. return api.VM_DEPLOYING
  328. case "LAUNCH_FAILED":
  329. return api.VM_DEPLOY_FAILED
  330. case "RUNNING":
  331. return api.VM_RUNNING
  332. case "STOPPED":
  333. return api.VM_READY
  334. case "STARTING", "REBOOTING":
  335. return api.VM_STARTING
  336. case "STOPPING":
  337. return api.VM_STOPPING
  338. case "SHUTDOWN":
  339. return api.VM_DEALLOCATED
  340. case "TERMINATING":
  341. return api.VM_DELETING
  342. default:
  343. return api.VM_UNKNOWN
  344. }
  345. }
  346. func (self *SInstance) Refresh() error {
  347. new, err := self.host.zone.region.GetInstance(self.InstanceId)
  348. if err != nil {
  349. return err
  350. }
  351. return jsonutils.Update(self, new)
  352. }
  353. func (self *SInstance) GetHypervisor() string {
  354. return api.HYPERVISOR_QCLOUD
  355. }
  356. func (self *SInstance) StartVM(ctx context.Context) error {
  357. err := cloudprovider.Wait(time.Second*15, time.Minute*5, func() (bool, error) {
  358. err := self.Refresh()
  359. if err != nil {
  360. return true, errors.Wrapf(err, "Refresh")
  361. }
  362. log.Debugf("status %s expect %s", self.GetStatus(), api.VM_RUNNING)
  363. if self.GetStatus() == api.VM_RUNNING {
  364. return true, nil
  365. }
  366. if self.GetStatus() == api.VM_STARTING {
  367. return false, nil
  368. }
  369. if self.GetStatus() == api.VM_READY {
  370. err := self.host.zone.region.StartVM(self.InstanceId)
  371. if err != nil {
  372. if e, ok := errors.Cause(err).(*sdkerrors.TencentCloudSDKError); ok {
  373. if e.Code == "UnsupportedOperation.InstanceStateRunning" {
  374. return true, nil
  375. }
  376. }
  377. return true, err
  378. }
  379. }
  380. return false, nil
  381. })
  382. return errors.Wrapf(err, "Wait")
  383. }
  384. func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
  385. err := self.host.zone.region.StopVM(self.InstanceId, opts)
  386. if err != nil {
  387. return err
  388. }
  389. return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 8*time.Minute) // 8 mintues, 腾讯云有时关机比较慢
  390. }
  391. func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
  392. url, err := self.host.zone.region.GetInstanceVNCUrl(self.InstanceId)
  393. if err != nil {
  394. return nil, err
  395. }
  396. ret := &cloudprovider.ServerVncOutput{
  397. Url: url,
  398. Protocol: "qcloud",
  399. InstanceId: self.InstanceId,
  400. Hypervisor: api.HYPERVISOR_QCLOUD,
  401. }
  402. return ret, nil
  403. }
  404. func (self *SInstance) UpdateVM(ctx context.Context, input cloudprovider.SInstanceUpdateOptions) error {
  405. return self.host.zone.region.UpdateVM(self.InstanceId, input.NAME)
  406. }
  407. func (self *SInstance) DeployVM(ctx context.Context, opts *cloudprovider.SInstanceDeployOptions) error {
  408. return self.host.zone.region.DeployVM(self.InstanceId, opts)
  409. }
  410. func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
  411. keypair := ""
  412. if len(desc.PublicKey) > 0 {
  413. var err error
  414. keypair, err = self.host.zone.region.syncKeypair(desc.PublicKey)
  415. if err != nil {
  416. return "", err
  417. }
  418. }
  419. err := self.host.zone.region.ReplaceSystemDisk(self.InstanceId, desc.ImageId, desc.Password, keypair, desc.SysSizeGB)
  420. if err != nil {
  421. return "", err
  422. }
  423. opts := &cloudprovider.ServerStopOptions{
  424. IsForce: true,
  425. }
  426. self.StopVM(ctx, opts)
  427. instance, err := self.host.zone.region.GetInstance(self.InstanceId)
  428. if err != nil {
  429. return "", err
  430. }
  431. return instance.SystemDisk.DiskId, nil
  432. }
  433. func (self *SInstance) ChangeConfig(ctx context.Context, opts *cloudprovider.SManagedVMChangeConfig) error {
  434. return self.host.zone.region.ChangeVMConfig(self.InstanceId, opts.InstanceType)
  435. }
  436. func (self *SInstance) GetModificationTypes() ([]cloudprovider.SInstanceModificationType, error) {
  437. return self.host.zone.region.GetInstanceModificationTypes(self.InstanceId)
  438. }
  439. func (self *SRegion) GetInstanceModificationTypes(instanceId string) ([]cloudprovider.SInstanceModificationType, error) {
  440. params := map[string]string{
  441. "Region": self.Region,
  442. "InstanceIds.0": instanceId,
  443. }
  444. body, err := self.cvmRequest("DescribeInstancesModification", params, true)
  445. if err != nil {
  446. return nil, errors.Wrapf(err, "DescribeInstancesModification")
  447. }
  448. resp := struct {
  449. InstanceTypeConfigStatusSet []struct {
  450. InstanceTypeConfig struct {
  451. InstanceType string
  452. Zone string
  453. }
  454. Status string
  455. }
  456. }{}
  457. if err := body.Unmarshal(&resp); err != nil {
  458. return nil, errors.Wrapf(err, "body.Unmarshal")
  459. }
  460. ret := make([]cloudprovider.SInstanceModificationType, 0)
  461. seen := map[string]struct{}{}
  462. for _, cfg := range resp.InstanceTypeConfigStatusSet {
  463. if !strings.EqualFold(cfg.Status, "SELL") {
  464. continue
  465. }
  466. if _, ok := seen[cfg.InstanceTypeConfig.InstanceType]; ok {
  467. continue
  468. }
  469. seen[cfg.InstanceTypeConfig.InstanceType] = struct{}{}
  470. ret = append(ret, cloudprovider.SInstanceModificationType{InstanceType: cfg.InstanceTypeConfig.InstanceType})
  471. }
  472. return ret, nil
  473. }
  474. func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
  475. return self.host.zone.region.AttachDisk(self.InstanceId, diskId)
  476. }
  477. func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
  478. return self.host.zone.region.DetachDisk(self.InstanceId, diskId)
  479. }
  480. func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) {
  481. instances, _, err := self.GetInstances("", []string{instanceId}, 0, 1)
  482. if err != nil {
  483. return nil, err
  484. }
  485. if len(instances) == 0 {
  486. return nil, cloudprovider.ErrNotFound
  487. }
  488. if len(instances) > 1 {
  489. return nil, cloudprovider.ErrDuplicateId
  490. }
  491. if instances[0].InstanceState == "LAUNCH_FAILED" {
  492. return nil, cloudprovider.ErrNotFound
  493. }
  494. return &instances[0], nil
  495. }
  496. func (self *SRegion) CreateInstance(name, hostname string, imageId string, instanceType string, securityGroupIds []string,
  497. zoneId string, desc string, passwd string, disks []SDisk, networkId string, ipAddr string,
  498. keypair string, userData string, bc *billing.SBillingCycle, projectId string,
  499. publicIpBw int, publicIpChargeType cloudprovider.TElasticipChargeType,
  500. tags map[string]string, osType string,
  501. ) (string, error) {
  502. params := make(map[string]string)
  503. params["Region"] = self.Region
  504. params["ImageId"] = imageId
  505. params["InstanceType"] = instanceType
  506. for i, id := range securityGroupIds {
  507. params[fmt.Sprintf("SecurityGroupIds.%d", i)] = id
  508. }
  509. params["Placement.Zone"] = zoneId
  510. if len(projectId) > 0 {
  511. params["Placement.ProjectId"] = projectId
  512. }
  513. params["InstanceName"] = name
  514. if len(hostname) > 0 {
  515. params["HostName"] = hostname
  516. }
  517. bandwidth := publicIpBw
  518. if publicIpBw == 0 {
  519. bandwidth = 100
  520. if bc != nil {
  521. bandwidth = 200
  522. }
  523. }
  524. internetChargeType := "TRAFFIC_POSTPAID_BY_HOUR"
  525. if publicIpChargeType == cloudprovider.ElasticipChargeTypeByBandwidth {
  526. internetChargeType = "BANDWIDTH_POSTPAID_BY_HOUR"
  527. }
  528. pkgs, _, err := self.GetBandwidthPackages([]string{}, 0, 50)
  529. if err != nil {
  530. return "", errors.Wrapf(err, "GetBandwidthPackages")
  531. }
  532. if len(pkgs) > 0 {
  533. bandwidth = 65535 // unlimited bandwidth
  534. if publicIpBw > 0 { // 若用户指定带宽则限制带宽大小
  535. bandwidth = publicIpBw
  536. }
  537. internetChargeType = "BANDWIDTH_PACKAGE"
  538. pkgId := pkgs[0].BandwidthPackageId
  539. for _, pkg := range pkgs {
  540. if len(pkg.ResourceSet) < 100 {
  541. pkgId = pkg.BandwidthPackageId
  542. break
  543. }
  544. }
  545. params["InternetAccessible.BandwidthPackageId"] = pkgId
  546. }
  547. params["InternetAccessible.InternetChargeType"] = internetChargeType
  548. params["InternetAccessible.InternetMaxBandwidthOut"] = fmt.Sprintf("%d", bandwidth)
  549. params["InternetAccessible.PublicIpAssigned"] = "TRUE"
  550. if publicIpBw == 0 {
  551. params["InternetAccessible.PublicIpAssigned"] = "FALSE"
  552. }
  553. if len(keypair) > 0 {
  554. params["LoginSettings.KeyIds.0"] = keypair
  555. } else if len(passwd) > 0 {
  556. params["LoginSettings.Password"] = passwd
  557. } else {
  558. params["LoginSettings.KeepImageLogin"] = "TRUE"
  559. }
  560. if len(userData) > 0 {
  561. params["UserData"] = userData
  562. }
  563. if bc != nil {
  564. params["InstanceChargeType"] = "PREPAID"
  565. params["InstanceChargePrepaid.Period"] = fmt.Sprintf("%d", bc.GetMonths())
  566. if bc.AutoRenew {
  567. params["InstanceChargePrepaid.RenewFlag"] = "NOTIFY_AND_AUTO_RENEW"
  568. } else {
  569. params["InstanceChargePrepaid.RenewFlag"] = "NOTIFY_AND_MANUAL_RENEW"
  570. }
  571. } else {
  572. params["InstanceChargeType"] = "POSTPAID_BY_HOUR"
  573. }
  574. // tags
  575. if len(tags) > 0 {
  576. params["TagSpecification.0.ResourceType"] = "instance"
  577. tagIdx := 0
  578. for k, v := range tags {
  579. params[fmt.Sprintf("TagSpecification.0.Tags.%d.Key", tagIdx)] = k
  580. params[fmt.Sprintf("TagSpecification.0.Tags.%d.Value", tagIdx)] = v
  581. tagIdx += 1
  582. }
  583. }
  584. //params["IoOptimized"] = "optimized"
  585. for i, d := range disks {
  586. if i == 0 {
  587. params["SystemDisk.DiskType"] = d.DiskType
  588. params["SystemDisk.DiskSize"] = fmt.Sprintf("%d", d.DiskSize)
  589. } else {
  590. params[fmt.Sprintf("DataDisks.%d.DiskSize", i-1)] = fmt.Sprintf("%d", d.DiskSize)
  591. params[fmt.Sprintf("DataDisks.%d.DiskType", i-1)] = d.DiskType
  592. }
  593. }
  594. network, err := self.GetNetwork(networkId)
  595. if err != nil {
  596. return "", errors.Wrapf(err, "GetNetwork(%s)", networkId)
  597. }
  598. params["VirtualPrivateCloud.SubnetId"] = networkId
  599. params["VirtualPrivateCloud.VpcId"] = network.VpcId
  600. if len(ipAddr) > 0 {
  601. params["VirtualPrivateCloud.PrivateIpAddresses.0"] = ipAddr
  602. }
  603. var body jsonutils.JSONObject
  604. instanceIdSet := []string{}
  605. err = cloudprovider.Wait(time.Second*10, time.Minute, func() (bool, error) {
  606. params["ClientToken"] = utils.GenRequestId(20)
  607. body, err = self.cvmRequest("RunInstances", params, true)
  608. if err != nil {
  609. if strings.Contains(err.Error(), "Code=InvalidPermission") { // 带宽上移用户未指定公网ip时不能设置带宽
  610. delete(params, "InternetAccessible.InternetChargeType")
  611. delete(params, "InternetAccessible.InternetMaxBandwidthOut")
  612. return false, nil
  613. }
  614. if strings.Contains(err.Error(), "UnsupportedOperation.BandwidthPackageIdNotSupported") ||
  615. (strings.Contains(err.Error(), "Code=InvalidParameterCombination") && strings.Contains(err.Error(), "InternetAccessible.BandwidthPackageId")) {
  616. delete(params, "InternetAccessible.BandwidthPackageId")
  617. return false, nil
  618. }
  619. return false, errors.Wrapf(err, "RunInstances")
  620. }
  621. return true, nil
  622. })
  623. if err != nil {
  624. return "", errors.Wrap(err, "RunInstances")
  625. }
  626. err = body.Unmarshal(&instanceIdSet, "InstanceIdSet")
  627. if err == nil && len(instanceIdSet) > 0 {
  628. return instanceIdSet[0], nil
  629. }
  630. return "", fmt.Errorf("Failed to create instance")
  631. }
  632. func (self *SRegion) doStartVM(instanceId string) error {
  633. return self.instanceOperation(instanceId, "StartInstances", nil, true)
  634. }
  635. func (self *SRegion) doStopVM(instanceId string, opts *cloudprovider.ServerStopOptions) error {
  636. params := make(map[string]string)
  637. if opts.IsForce {
  638. // params["ForceStop"] = "FALSE"
  639. params["StopType"] = "HARD"
  640. } else {
  641. // params["ForceStop"] = "FALSE"
  642. params["StopType"] = "SOFT"
  643. }
  644. if opts.StopCharging {
  645. params["StoppedMode"] = "STOP_CHARGING"
  646. }
  647. return self.instanceOperation(instanceId, "StopInstances", params, true)
  648. }
  649. func (self *SRegion) doDeleteVM(instanceId string) error {
  650. params := make(map[string]string)
  651. err := self.instanceOperation(instanceId, "TerminateInstances", params, true)
  652. if err != nil && cloudprovider.IsError(err, []string{"InvalidInstanceId.NotFound"}) {
  653. return nil
  654. }
  655. return err
  656. }
  657. func (self *SRegion) StartVM(instanceId string) error {
  658. status, err := self.GetInstanceStatus(instanceId)
  659. if err != nil {
  660. log.Errorf("Fail to get instance status on StartVM: %s", err)
  661. return err
  662. }
  663. if status != InstanceStatusStopped {
  664. log.Errorf("StartVM: vm status is %s expect %s", status, InstanceStatusStopped)
  665. return cloudprovider.ErrInvalidStatus
  666. }
  667. return self.doStartVM(instanceId)
  668. }
  669. func (self *SRegion) StopVM(instanceId string, opts *cloudprovider.ServerStopOptions) error {
  670. status, err := self.GetInstanceStatus(instanceId)
  671. if err != nil {
  672. log.Errorf("Fail to get instance status on StopVM: %s", err)
  673. return err
  674. }
  675. if status == InstanceStatusStopped {
  676. return nil
  677. }
  678. return self.doStopVM(instanceId, opts)
  679. }
  680. func (self *SRegion) DeleteVM(instanceId string) error {
  681. return self.doDeleteVM(instanceId)
  682. }
  683. func (self *SRegion) DeployVM(instanceId string, opts *cloudprovider.SInstanceDeployOptions) error {
  684. instance, err := self.GetInstance(instanceId)
  685. if err != nil {
  686. return err
  687. }
  688. // 修改密钥时直接返回
  689. if opts.DeleteKeypair {
  690. for i := 0; i < len(instance.LoginSettings.KeyIds); i++ {
  691. err = self.DetachKeyPair(instanceId, instance.LoginSettings.KeyIds[i])
  692. if err != nil {
  693. return err
  694. }
  695. }
  696. }
  697. if len(opts.PublicKey) > 0 {
  698. keypairName, err := self.syncKeypair(opts.PublicKey)
  699. if err != nil {
  700. return err
  701. }
  702. err = self.AttachKeypair(instanceId, keypairName)
  703. if err != nil {
  704. return err
  705. }
  706. }
  707. if len(opts.Password) > 0 {
  708. return self.instanceOperation(instanceId, "ResetInstancesPassword", map[string]string{"Password": opts.Password}, true)
  709. }
  710. return nil
  711. }
  712. func (self *SInstance) DeleteVM(ctx context.Context) error {
  713. diskIds := []string{}
  714. for _, disk := range self.DataDisks {
  715. if !disk.DeleteWithInstance {
  716. diskIds = append(diskIds, disk.DiskId)
  717. }
  718. }
  719. err := self.host.zone.region.DeleteVM(self.InstanceId)
  720. if err != nil {
  721. return errors.Wrapf(err, "region.DeleteVM(%s)", self.InstanceId)
  722. }
  723. if self.GetBillingType() == billing_api.BILLING_TYPE_PREPAID { // 预付费的需要删除两次
  724. cloudprovider.Wait(time.Second*10, time.Minute*5, func() (bool, error) {
  725. err = self.Refresh()
  726. if err != nil {
  727. log.Warningf("refresh instance %s(%s) error: %v", self.InstanceName, self.InstanceId, err)
  728. return false, nil
  729. }
  730. // 需要等待第一次删除后,状态变为SHUTDOWN,才可以进行第二次删除,否则会报:
  731. // Code=OperationDenied.InstanceOperationInProgress, Message=该实例`['ins-mxqturgj']`
  732. if self.InstanceState == "SHUTDOWN" {
  733. return true, nil
  734. }
  735. log.Debugf("wait %s(%s) status be SHUTDOWN, current is %s", self.InstanceName, self.InstanceId, self.InstanceState)
  736. return false, nil
  737. })
  738. err := self.host.zone.region.DeleteVM(self.InstanceId)
  739. if err != nil {
  740. return errors.Wrapf(err, "region.DeleteVM(%s)", self.InstanceId)
  741. }
  742. }
  743. err = cloudprovider.WaitDeleted(self, 10*time.Second, 5*time.Minute) // 5minutes
  744. if err != nil {
  745. return errors.Wrapf(err, "WaitDeleted")
  746. }
  747. if len(diskIds) > 0 {
  748. err = self.host.zone.region.DeleteDisk(diskIds)
  749. if err != nil {
  750. return errors.Wrapf(err, "DeleteDisk")
  751. }
  752. }
  753. return nil
  754. }
  755. func (self *SRegion) UpdateVM(instanceId string, name string) error {
  756. params := make(map[string]string)
  757. params["InstanceName"] = name
  758. return self.modifyInstanceAttribute(instanceId, params)
  759. }
  760. func (self *SRegion) modifyInstanceAttribute(instanceId string, params map[string]string) error {
  761. return self.instanceOperation(instanceId, "ModifyInstancesAttribute", params, true)
  762. }
  763. func (self *SRegion) ReplaceSystemDisk(instanceId string, imageId string, passwd string, keypairName string, sysDiskSizeGB int) error {
  764. params := make(map[string]string)
  765. params["InstanceId"] = instanceId
  766. params["ImageId"] = imageId
  767. params["EnhancedService.SecurityService.Enabled"] = "TRUE"
  768. params["EnhancedService.MonitorService.Enabled"] = "TRUE"
  769. // 秘钥和密码及保留镜像设置只能选其一
  770. if len(keypairName) > 0 {
  771. params["LoginSettings.KeyIds.0"] = keypairName
  772. } else if len(passwd) > 0 {
  773. params["LoginSettings.Password"] = passwd
  774. } else {
  775. params["LoginSettings.KeepImageLogin"] = "TRUE"
  776. }
  777. if sysDiskSizeGB > 0 {
  778. params["SystemDisk.DiskSize"] = fmt.Sprintf("%d", sysDiskSizeGB)
  779. }
  780. _, err := self.cvmRequest("ResetInstance", params, true)
  781. return err
  782. }
  783. func (self *SRegion) ChangeVMConfig(instanceId string, instanceType string) error {
  784. params := make(map[string]string)
  785. params["InstanceType"] = instanceType
  786. err := self.instanceOperation(instanceId, "ResetInstancesType", params, true)
  787. return errors.Wrapf(err, "ResetInstancesType %s", instanceType)
  788. }
  789. func (self *SRegion) DetachDisk(instanceId string, diskId string) error {
  790. params := make(map[string]string)
  791. params["DiskIds.0"] = diskId
  792. log.Infof("Detach instance %s disk %s", instanceId, diskId)
  793. _, err := self.cbsRequest("DetachDisks", params)
  794. if err != nil {
  795. // 可重复卸载,无报错,若磁盘被删除会有以下错误
  796. //[TencentCloudSDKError] Code=InvalidDisk.NotSupported, Message=disk(disk-4g5s7zhl) deleted (39a711ce2d17), RequestId=508d7fe3-e64e-4bb8-8ad7-39a711ce2d17
  797. if strings.Contains(err.Error(), fmt.Sprintf("disk(%s) deleted", diskId)) {
  798. return nil
  799. }
  800. return errors.Wrap(err, "DetachDisks")
  801. }
  802. return nil
  803. }
  804. func (self *SRegion) AttachDisk(instanceId string, diskId string) error {
  805. params := make(map[string]string)
  806. params["InstanceId"] = instanceId
  807. params["DeleteWithInstance"] = "True"
  808. params["DiskIds.0"] = diskId
  809. _, err := self.cbsRequest("AttachDisks", params)
  810. if err != nil {
  811. return errors.Wrapf(err, "AttachDisks %s => %s", diskId, instanceId)
  812. }
  813. return nil
  814. }
  815. func (self *SInstance) SetSecurityGroups(secgroupIds []string) error {
  816. params := map[string]string{}
  817. for i := 0; i < len(secgroupIds); i++ {
  818. params[fmt.Sprintf("SecurityGroups.%d", i)] = secgroupIds[i]
  819. }
  820. return self.host.zone.region.instanceOperation(self.InstanceId, "ModifyInstancesAttribute", params, true)
  821. }
  822. func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
  823. eip, total, err := self.host.zone.region.GetEips("", self.InstanceId, 0, 1)
  824. if err != nil {
  825. return nil, err
  826. }
  827. if total == 1 {
  828. return &eip[0], nil
  829. }
  830. self.Refresh()
  831. for _, address := range self.PublicIpAddresses {
  832. eip := SEipAddress{region: self.host.zone.region}
  833. eip.AddressIp = address
  834. eip.InstanceId = self.InstanceId
  835. eip.AddressId = self.InstanceId
  836. eip.AddressName = address
  837. eip.AddressType = EIP_TYPE_WANIP
  838. eip.AddressStatus = EIP_STATUS_BIND
  839. eip.Bandwidth = self.InternetAccessible.InternetMaxBandwidthOut
  840. return &eip, nil
  841. }
  842. return nil, nil
  843. }
  844. func (self *SInstance) GetBillingType() string {
  845. switch self.InstanceChargeType {
  846. case PrePaidInstanceChargeType:
  847. return billing_api.BILLING_TYPE_PREPAID
  848. case PostPaidInstanceChargeType:
  849. return billing_api.BILLING_TYPE_POSTPAID
  850. default:
  851. return billing_api.BILLING_TYPE_PREPAID
  852. }
  853. }
  854. func (self *SInstance) ChangeBillingType(billingType string) error {
  855. return self.host.zone.region.ModifyInstanceChargeType(self.InstanceId, billingType)
  856. }
  857. func (region *SRegion) ModifyInstanceChargeType(vmId string, billingType string) error {
  858. params := map[string]string{
  859. "Region": region.Region,
  860. "ModifyPortableDataDisk": "true",
  861. "InstanceIds.0": vmId,
  862. }
  863. switch billingType {
  864. case billing_api.BILLING_TYPE_POSTPAID:
  865. params["InstanceChargeType"] = "POSTPAID_BY_HOUR"
  866. case billing_api.BILLING_TYPE_PREPAID:
  867. params["InstanceChargeType"] = "PREPAID"
  868. params["InstanceChargePrepaid.Period"] = "1"
  869. params["InstanceChargePrepaid.RenewFlag"] = "NOTIFY_AND_AUTO_RENEW"
  870. default:
  871. return fmt.Errorf("invalid billing_type %s", billingType)
  872. }
  873. _, err := region.cvmRequest("ModifyInstancesChargeType", params, false)
  874. return err
  875. }
  876. func (self *SInstance) GetCreatedAt() time.Time {
  877. return self.CreatedTime
  878. }
  879. func (self *SInstance) GetExpiredAt() time.Time {
  880. return self.ExpiredTime
  881. }
  882. func (self *SInstance) UpdateUserData(userData string) error {
  883. return cloudprovider.ErrNotSupported
  884. }
  885. func (self *SInstance) Renew(bc billing.SBillingCycle) error {
  886. return self.host.zone.region.RenewInstances([]string{self.InstanceId}, bc)
  887. }
  888. func (region *SRegion) RenewInstances(instanceId []string, bc billing.SBillingCycle) error {
  889. params := make(map[string]string)
  890. for i := 0; i < len(instanceId); i += 1 {
  891. params[fmt.Sprintf("InstanceIds.%d", i)] = instanceId[i]
  892. }
  893. params["InstanceChargePrepaid.Period"] = fmt.Sprintf("%d", bc.GetMonths())
  894. params["InstanceChargePrepaid.RenewFlag"] = "NOTIFY_AND_MANUAL_RENEW"
  895. params["RenewPortableDataDisk"] = "TRUE"
  896. _, err := region.cvmRequest("RenewInstances", params, true)
  897. if err != nil {
  898. log.Errorf("RenewInstance fail %s", err)
  899. return err
  900. }
  901. return nil
  902. }
  903. func (self *SInstance) GetProjectId() string {
  904. return strconv.Itoa(self.Placement.ProjectId)
  905. }
  906. func (self *SInstance) GetError() error {
  907. return nil
  908. }
  909. func (region *SRegion) ConvertPublicIpToEip(instanceId string) error {
  910. params := map[string]string{
  911. "InstanceId": instanceId,
  912. "Region": region.Region,
  913. }
  914. _, err := region.vpcRequest("TransformAddress", params)
  915. if err != nil {
  916. log.Errorf("TransformAddress fail %s", err)
  917. return err
  918. }
  919. return nil
  920. }
  921. func (self *SInstance) ConvertPublicIpToEip() error {
  922. return self.host.zone.region.ConvertPublicIpToEip(self.InstanceId)
  923. }
  924. func (self *SInstance) IsAutoRenew() bool {
  925. return self.RenewFlag == "NOTIFY_AND_AUTO_RENEW"
  926. }
  927. // https://cloud.tencent.com/document/api/213/15752
  928. func (region *SRegion) SetInstanceAutoRenew(instanceId string, autoRenew bool) error {
  929. params := map[string]string{
  930. "InstanceIds.0": instanceId,
  931. "Region": region.Region,
  932. "RenewFlag": "NOTIFY_AND_MANUAL_RENEW",
  933. }
  934. if autoRenew {
  935. params["RenewFlag"] = "NOTIFY_AND_AUTO_RENEW"
  936. }
  937. _, err := region.cvmRequest("ModifyInstancesRenewFlag", params, true)
  938. return err
  939. }
  940. func (self *SInstance) SetAutoRenew(bc billing.SBillingCycle) error {
  941. return self.host.zone.region.SetInstanceAutoRenew(self.InstanceId, bc.AutoRenew)
  942. }
  943. func (self *SInstance) SetTags(tags map[string]string, replace bool) error {
  944. return self.host.zone.region.SetResourceTags("cvm", "instance", []string{self.InstanceId}, tags, replace)
  945. }
  946. func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
  947. params := map[string]string{
  948. "ImageName": opts.Name,
  949. "InstanceId": instanceId,
  950. }
  951. if len(opts.Notes) > 0 {
  952. params["ImageDescription"] = opts.Notes
  953. }
  954. resp, err := self.cvmRequest("CreateImage", params, true)
  955. if err != nil {
  956. return nil, errors.Wrapf(err, "CreateImage")
  957. }
  958. ret := struct{ ImageId string }{}
  959. err = resp.Unmarshal(&ret)
  960. if err != nil {
  961. return nil, errors.Wrapf(err, "resp.Unmarshal")
  962. }
  963. imageIds := []string{}
  964. if len(ret.ImageId) > 0 {
  965. imageIds = append(imageIds, ret.ImageId)
  966. }
  967. images, _, err := self.GetImages("", "PRIVATE_IMAGE", imageIds, opts.Name, 0, 1)
  968. if err != nil {
  969. return nil, errors.Wrapf(err, "GetImage(%s,%s)", opts.Name, ret.ImageId)
  970. }
  971. for i := range images {
  972. if images[i].ImageId == ret.ImageId || images[i].ImageName == opts.Name {
  973. images[i].storageCache = self.getStoragecache()
  974. return &images[i], nil
  975. }
  976. }
  977. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "after save image %s", opts.Name)
  978. }
  979. func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
  980. image, err := self.host.zone.region.SaveImage(self.InstanceId, opts)
  981. if err != nil {
  982. return nil, errors.Wrapf(err, "SaveImage")
  983. }
  984. return image, nil
  985. }