instance.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  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 ecloud
  15. import (
  16. "context"
  17. "fmt"
  18. "time"
  19. "yunion.io/x/jsonutils"
  20. "yunion.io/x/log"
  21. "yunion.io/x/pkg/errors"
  22. "yunion.io/x/pkg/util/billing"
  23. "yunion.io/x/pkg/util/imagetools"
  24. billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
  25. api "yunion.io/x/cloudmux/pkg/apis/compute"
  26. "yunion.io/x/cloudmux/pkg/cloudprovider"
  27. "yunion.io/x/cloudmux/pkg/multicloud"
  28. )
  29. type SInstance struct {
  30. multicloud.SInstanceBase
  31. EcloudTags
  32. multicloud.SBillingBase
  33. SZoneRegionBase
  34. SCreateTime
  35. host *SHost
  36. region *SRegion
  37. billInfoFetched bool
  38. billInfo *SInstanceBillInfo
  39. // OpenAPI v4/list/describe-instances 返回字段
  40. ActualSystemImage string `json:"actualSystemImage"`
  41. AdminPaused bool `json:"adminPaused"`
  42. AdminPausedTip string `json:"adminPausedTip"`
  43. ZoneId string `json:"zoneId"`
  44. CredibleStatus string `json:"credibleStatus"`
  45. CrossRegionMigrate bool `json:"crossRegionMigrate"`
  46. Description string `json:"description"`
  47. GroupId string `json:"groupId"`
  48. InstanceId string `json:"instanceId"`
  49. InstanceName string `json:"instanceName"`
  50. ImageId string `json:"imageId"`
  51. ImageName string `json:"imageName"`
  52. ImageOsType string `json:"imageOsType"`
  53. KeyName string `json:"keyName"`
  54. MainPortId string `json:"mainPortId"`
  55. MaxPorts int `json:"maxPorts"`
  56. MaxVolumes int `json:"maxVolumes"`
  57. AvailableZone string `json:"availableZone"`
  58. ProductType string `json:"productType"`
  59. Recycle bool `json:"recycle"`
  60. RecycleCount int `json:"recycleCount"`
  61. RecycleStatus string `json:"recycleStatus"`
  62. RecycleTime string `json:"recycleTime"`
  63. ReleaseProtect bool `json:"releaseProtect"`
  64. FlavorName string `json:"flavorName"`
  65. ServerType string `json:"serverType"`
  66. ServerVmType string `json:"serverVmType"`
  67. StoppedMode bool `json:"stoppedMode"`
  68. SupportVolumeMount string `json:"supportVolumeMount"`
  69. UserName string `json:"userName"`
  70. Vcpu int `json:"vcpu"`
  71. Vmemory int `json:"vmemory"`
  72. Vdisk int `json:"vdisk"`
  73. SystemDiskId string `json:"systemDiskId"`
  74. Status string `json:"status"`
  75. BootVolumeType string `json:"bootVolumeType"`
  76. }
  77. func (i *SInstance) GetBillingType() string {
  78. region := i.getRegion()
  79. if region != nil && !i.billInfoFetched {
  80. i.billInfoFetched = true
  81. infos, err := region.GetInsatnceBillInfo(context.Background(), []string{i.GetId()})
  82. if err != nil {
  83. log.Debugf("ecloud GetInsatnceBillInfo(%s) error: %v", i.GetId(), err)
  84. } else {
  85. for idx := range infos {
  86. if infos[idx].InstanceId == i.GetId() {
  87. i.billInfo = &infos[idx]
  88. break
  89. }
  90. }
  91. }
  92. }
  93. if i.billInfo != nil {
  94. // chargingMode: 1=包周期(预付费) 2=按需(后付费)
  95. switch i.billInfo.ChargingMode {
  96. case 1:
  97. return billing_api.BILLING_TYPE_PREPAID
  98. case 2:
  99. return billing_api.BILLING_TYPE_POSTPAID
  100. }
  101. }
  102. // 查询失败或无返回时兜底按后付费处理
  103. return billing_api.BILLING_TYPE_POSTPAID
  104. }
  105. func (i *SInstance) getRegion() *SRegion {
  106. if i.host != nil && i.host.zone != nil && i.host.zone.region != nil {
  107. return i.host.zone.region
  108. }
  109. return i.region
  110. }
  111. func (i *SInstance) GetExpiredAt() time.Time {
  112. return time.Time{}
  113. }
  114. func (i *SInstance) GetId() string {
  115. return i.InstanceId
  116. }
  117. func (i *SInstance) GetName() string {
  118. return i.InstanceName
  119. }
  120. func (i *SInstance) GetHostname() string {
  121. return ""
  122. }
  123. func (i *SInstance) GetGlobalId() string {
  124. return i.GetId()
  125. }
  126. func (i *SInstance) GetStatus() string {
  127. switch i.Status {
  128. case "active":
  129. return api.VM_RUNNING
  130. case "suspended", "paused":
  131. return api.VM_SUSPEND
  132. case "build", "rebuild", "resize", "verify_resize", "revert_resize", "password":
  133. return api.VM_STARTING
  134. case "reboot", "hard_reboot":
  135. return api.VM_STOPPING
  136. case "stopped", "shutoff":
  137. return api.VM_READY
  138. case "migrating":
  139. return api.VM_MIGRATING
  140. case "backuping":
  141. return api.VM_BACKUP_CREATING
  142. default:
  143. return api.VM_UNKNOWN
  144. }
  145. }
  146. func (i *SInstance) Refresh() error {
  147. vm, err := i.host.zone.region.GetInstance(i.InstanceId)
  148. if err != nil {
  149. return err
  150. }
  151. return jsonutils.Update(i, vm)
  152. }
  153. func (self *SInstance) GetBootOrder() string {
  154. return "dcn"
  155. }
  156. func (self *SInstance) GetVga() string {
  157. return "std"
  158. }
  159. func (self *SInstance) GetVdi() string {
  160. return "vnc"
  161. }
  162. func (i *SInstance) GetOsType() cloudprovider.TOsType {
  163. return cloudprovider.TOsType(i.ImageOsType)
  164. }
  165. func (i *SInstance) GetFullOsName() string {
  166. return i.ImageName
  167. }
  168. func (i *SInstance) GetBios() cloudprovider.TBiosType {
  169. return cloudprovider.BIOS
  170. }
  171. func (i *SInstance) GetOsArch() string {
  172. return ""
  173. }
  174. func (i *SInstance) GetOsDist() string {
  175. osInfo := imagetools.NormalizeImageInfo(i.ImageName, "", i.ImageOsType, "", "")
  176. return osInfo.OsDistro
  177. }
  178. func (i *SInstance) GetOsVersion() string {
  179. osInfo := imagetools.NormalizeImageInfo(i.ImageName, "", i.ImageOsType, "", "")
  180. return osInfo.OsVersion
  181. }
  182. func (i *SInstance) GetOsLang() string {
  183. osInfo := imagetools.NormalizeImageInfo(i.ImageName, "", i.ImageOsType, "", "")
  184. return osInfo.OsLang
  185. }
  186. func (i *SInstance) GetMachine() string {
  187. return "pc"
  188. }
  189. func (i *SInstance) GetInstanceType() string {
  190. return i.FlavorName
  191. }
  192. func (in *SInstance) GetProjectId() string {
  193. return ""
  194. }
  195. func (in *SInstance) GetIHost() cloudprovider.ICloudHost {
  196. return in.host
  197. }
  198. func (in *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
  199. sysDisk, err := in.GetSysDisk()
  200. if err != nil {
  201. return nil, err
  202. }
  203. dataDisks, err := in.GetDataDisks()
  204. if err != nil {
  205. return nil, err
  206. }
  207. return append([]cloudprovider.ICloudDisk{sysDisk}, dataDisks...), nil
  208. }
  209. func (in *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
  210. region := in.getRegion()
  211. if region == nil {
  212. return []cloudprovider.ICloudNic{}, nil
  213. }
  214. nics, err := region.GetInstanceNics(context.Background(), in.GetId())
  215. if err != nil {
  216. return nil, err
  217. }
  218. ret := make([]cloudprovider.ICloudNic, len(nics))
  219. for i := range nics {
  220. nics[i].instance = in
  221. ret[i] = &nics[i]
  222. }
  223. return ret, nil
  224. }
  225. func (in *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
  226. nics, err := in.host.zone.region.GetInstanceNics(context.Background(), in.GetId())
  227. if err != nil {
  228. return nil, err
  229. }
  230. for i := range nics {
  231. if len(nics[i].FipAddress) == 0 {
  232. continue
  233. }
  234. eip, err := in.getRegion().GetEipByAddr(nics[i].FipAddress)
  235. if err != nil {
  236. return nil, err
  237. }
  238. return eip, nil
  239. }
  240. return nil, cloudprovider.ErrNotFound
  241. }
  242. func (in *SInstance) GetSecurityGroupIds() ([]string, error) {
  243. nics, err := in.host.zone.region.GetInstanceNics(context.Background(), in.GetId())
  244. if err != nil {
  245. return nil, err
  246. }
  247. securityGroupIds := make([]string, 0)
  248. for i := range nics {
  249. for _, sg := range nics[i].SecurityGroups {
  250. securityGroupIds = append(securityGroupIds, sg.Id)
  251. }
  252. }
  253. return securityGroupIds, nil
  254. }
  255. func (in *SInstance) GetVcpuCount() int {
  256. return in.Vcpu
  257. }
  258. func (in *SInstance) GetVmemSizeMB() int {
  259. return in.Vmemory
  260. }
  261. func (in *SInstance) SetSecurityGroups(ids []string) error {
  262. return cloudprovider.ErrNotImplemented
  263. }
  264. func (in *SInstance) GetHypervisor() string {
  265. return api.HYPERVISOR_ECLOUD
  266. }
  267. func (in *SInstance) StartVM(ctx context.Context) error {
  268. region := in.getRegion()
  269. if region == nil {
  270. return errors.Wrap(cloudprovider.ErrNotImplemented, "missing region")
  271. }
  272. return region.StartInstance(ctx, in.GetId())
  273. }
  274. func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
  275. region := self.getRegion()
  276. if region == nil {
  277. return errors.Wrap(cloudprovider.ErrNotImplemented, "missing region")
  278. }
  279. return region.StopInstance(ctx, self.GetId())
  280. }
  281. func (self *SInstance) DeleteVM(ctx context.Context) error {
  282. region := self.getRegion()
  283. if region == nil {
  284. return errors.Wrap(cloudprovider.ErrNotImplemented, "missing region")
  285. }
  286. return region.DeleteInstance(ctx, self.GetId(), false, false)
  287. }
  288. func (self *SInstance) UpdateVM(ctx context.Context, input cloudprovider.SInstanceUpdateOptions) error {
  289. return cloudprovider.ErrNotSupported
  290. }
  291. func (self *SInstance) UpdateUserData(userData string) error {
  292. return cloudprovider.ErrNotSupported
  293. }
  294. func (self *SInstance) RebuildRoot(ctx context.Context, config *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
  295. return "", cloudprovider.ErrNotImplemented
  296. }
  297. func (self *SInstance) DeployVM(ctx context.Context, opts *cloudprovider.SInstanceDeployOptions) error {
  298. return cloudprovider.ErrNotImplemented
  299. }
  300. func (in *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
  301. return errors.ErrNotImplemented
  302. }
  303. func (in *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
  304. url, err := in.host.zone.region.GetInstanceVNCUrl(in.GetId())
  305. if err != nil {
  306. return nil, err
  307. }
  308. ret := &cloudprovider.ServerVncOutput{
  309. Url: url,
  310. Protocol: "ecloud",
  311. InstanceId: in.GetId(),
  312. Hypervisor: api.HYPERVISOR_ECLOUD,
  313. }
  314. return ret, nil
  315. }
  316. func (in *SInstance) AttachDisk(ctx context.Context, diskId string) error {
  317. return cloudprovider.ErrNotImplemented
  318. }
  319. func (in *SInstance) DetachDisk(ctx context.Context, diskId string) error {
  320. return cloudprovider.ErrNotImplemented
  321. }
  322. func (self *SInstance) Renew(bc billing.SBillingCycle) error {
  323. return cloudprovider.ErrNotImplemented
  324. }
  325. func (self *SInstance) GetError() error {
  326. return nil
  327. }
  328. func (in *SInstance) GetSysDisk() (cloudprovider.ICloudDisk, error) {
  329. storage, err := in.host.zone.GetStorageByType(storageTypeConstMap[in.BootVolumeType])
  330. if err != nil {
  331. return nil, errors.Wrapf(err, "GetStorageByType(%s)", in.BootVolumeType)
  332. }
  333. disk := &SDisk{
  334. storage: storage,
  335. ManualAttr: SDiskManualAttr{
  336. IsVirtual: true,
  337. TemplateId: in.ImageId,
  338. ServerId: in.InstanceId,
  339. },
  340. SCreateTime: in.SCreateTime,
  341. SZoneRegionBase: in.SZoneRegionBase,
  342. ServerId: []string{in.InstanceId},
  343. IsShare: false,
  344. IsDelete: false,
  345. SizeGB: in.Vdisk,
  346. ID: in.SystemDiskId,
  347. Name: fmt.Sprintf("%s-root", in.InstanceName),
  348. Status: "in-use",
  349. Type: api.STORAGE_ECLOUD_SYSTEM,
  350. }
  351. return disk, nil
  352. }
  353. func (region *SRegion) GetDataDisks(serverId string) ([]SDisk, error) {
  354. request := NewOpenApiEbsRequest(region.RegionId, "/api/ebs/acl/v3/volume/mount/list", map[string]string{"serverId": serverId}, nil)
  355. var ret []SDisk
  356. err := region.client.doList(context.Background(), request.Base(), &ret)
  357. if err != nil {
  358. return nil, errors.Wrapf(err, "GetDataDisks")
  359. }
  360. return ret, nil
  361. }
  362. func (in *SInstance) GetDataDisks() ([]cloudprovider.ICloudDisk, error) {
  363. disks, err := in.host.zone.region.GetDataDisks(in.InstanceId)
  364. if err != nil {
  365. return nil, err
  366. }
  367. idisks := make([]cloudprovider.ICloudDisk, len(disks))
  368. for i := range disks {
  369. storage, err := in.host.zone.GetStorageByType(disks[i].Type)
  370. if err != nil {
  371. return nil, errors.Wrapf(err, "GetStorageByType(%s)", disks[i].Type)
  372. }
  373. disks[i].storage = storage
  374. idisks[i] = &disks[i]
  375. }
  376. return idisks, nil
  377. }
  378. func (r *SRegion) GetInstances(zoneId string, serverId string) ([]SInstance, error) {
  379. body := jsonutils.NewDict()
  380. if len(zoneId) > 0 {
  381. body.Add(jsonutils.NewString(zoneId), "zoneId")
  382. }
  383. if len(serverId) > 0 {
  384. body.Add(jsonutils.NewString(serverId), "instanceId")
  385. }
  386. req := NewOpenApiInstanceRequest(r.RegionId, body)
  387. ret := make([]SInstance, 0)
  388. err := r.client.doPostList(context.Background(), req.Base(), &ret)
  389. if err != nil {
  390. return nil, err
  391. }
  392. for i := range ret {
  393. ret[i].region = r
  394. }
  395. return ret, nil
  396. }
  397. func (r *SRegion) GetInstance(id string) (*SInstance, error) {
  398. instances, err := r.GetInstances("", id)
  399. if err != nil {
  400. return nil, err
  401. }
  402. for i := range instances {
  403. if instances[i].InstanceId == id {
  404. instances[i].region = r
  405. return &instances[i], nil
  406. }
  407. }
  408. return nil, cloudprovider.ErrNotFound
  409. }
  410. func (r *SRegion) GetInstanceVNCUrl(instanceId string) (string, error) {
  411. req := NewOpenApiInstanceActionRequest(r.RegionId, "/api/openapi-instance/v4/vnc-url", nil)
  412. base := req.Base()
  413. base.SetMethod("GET")
  414. base.GetQueryParams()["instanceId"] = instanceId
  415. resp := struct {
  416. VncUrl string `json:"vncUrl"`
  417. }{}
  418. if err := r.client.doGet(context.Background(), base, &resp); err != nil {
  419. return "", err
  420. }
  421. return resp.VncUrl, nil
  422. }
  423. type sInstanceBatchResult struct {
  424. Result bool `json:"result"`
  425. InstanceId string `json:"instanceId"`
  426. Message string `json:"message"`
  427. }
  428. type sInstanceBatchResp struct {
  429. InstanceBatchResult []sInstanceBatchResult `json:"instanceBatchResult"`
  430. }
  431. func (r *SRegion) StartInstance(ctx context.Context, instanceId string) error {
  432. body := jsonutils.NewDict()
  433. body.Set("batchStartInstancesBody", jsonutils.Marshal(map[string][]string{
  434. "instanceIds": {instanceId},
  435. }))
  436. req := NewOpenApiInstanceActionRequest(r.RegionId, "/api/openapi-instance/v4/batch-start-instances", body)
  437. resp := sInstanceBatchResp{}
  438. if err := r.client.doPost(ctx, req.Base(), &resp); err != nil {
  439. return err
  440. }
  441. for i := range resp.InstanceBatchResult {
  442. if resp.InstanceBatchResult[i].InstanceId != instanceId {
  443. continue
  444. }
  445. if !resp.InstanceBatchResult[i].Result {
  446. return errors.Errorf("start instance %s failed: %s", instanceId, resp.InstanceBatchResult[i].Message)
  447. }
  448. return nil
  449. }
  450. return nil
  451. }
  452. func (r *SRegion) StopInstance(ctx context.Context, instanceId string) error {
  453. body := jsonutils.NewDict()
  454. body.Set("batchStopInstancesBody", jsonutils.Marshal(map[string][]string{
  455. "instanceIds": {instanceId},
  456. }))
  457. req := NewOpenApiInstanceActionRequest(r.RegionId, "/api/openapi-instance/v4/batch-stop-instances", body)
  458. resp := sInstanceBatchResp{}
  459. if err := r.client.doPost(ctx, req.Base(), &resp); err != nil {
  460. return err
  461. }
  462. for i := range resp.InstanceBatchResult {
  463. if resp.InstanceBatchResult[i].InstanceId != instanceId {
  464. continue
  465. }
  466. if !resp.InstanceBatchResult[i].Result {
  467. return errors.Errorf("stop instance %s failed: %s", instanceId, resp.InstanceBatchResult[i].Message)
  468. }
  469. return nil
  470. }
  471. return nil
  472. }
  473. func (r *SRegion) DeleteInstance(ctx context.Context, instanceId string, deletePublicNetwork, deleteDataVolumes bool) error {
  474. body := jsonutils.NewDict()
  475. body.Set("deleteInstancesBody", jsonutils.Marshal(map[string]interface{}{
  476. "instanceIds": []string{instanceId},
  477. "deletePublicNetwork": deletePublicNetwork,
  478. "deleteDataVolumes": deleteDataVolumes,
  479. "dryRun": false,
  480. }))
  481. req := NewOpenApiInstanceActionRequest(r.RegionId, "/api/openapi-instance/v4/delete-instances", body)
  482. resp := sInstanceBatchResp{}
  483. if err := r.client.doPost(ctx, req.Base(), &resp); err != nil {
  484. return err
  485. }
  486. for i := range resp.InstanceBatchResult {
  487. if resp.InstanceBatchResult[i].InstanceId != instanceId {
  488. continue
  489. }
  490. if !resp.InstanceBatchResult[i].Result {
  491. return errors.Errorf("delete instance %s failed: %s", instanceId, resp.InstanceBatchResult[i].Message)
  492. }
  493. return nil
  494. }
  495. return nil
  496. }
  497. type sDescribeVirtualNetworksResp struct {
  498. MacAddress string `json:"macAddress"`
  499. Name string `json:"name"`
  500. BandwidthSize int `json:"bandwidthSize"`
  501. FixedIpResps []struct {
  502. SubnetId string `json:"subnetId"`
  503. SubnetCidr string `json:"subnetCidr"`
  504. VpcName string `json:"vpcName"`
  505. IpVersion int32 `json:"ipVersion"`
  506. VpcId string `json:"vpcId"`
  507. IpAddress string `json:"ipAddress"`
  508. SubnetName string `json:"subnetName"`
  509. } `json:"fixedIpResps"`
  510. CreatedTime string `json:"createdTime"`
  511. PublicIp string `json:"publicIp"`
  512. Id string `json:"id"`
  513. SubnetName string `json:"subnetName"`
  514. SecurityGroupResps []struct {
  515. Name string `json:"name"`
  516. CreatedTime string `json:"createdTime"`
  517. Description string `json:"description"`
  518. Id string `json:"id"`
  519. } `json:"securityGroupResps"`
  520. }
  521. // GetInstanceNics 查询实例绑定的网卡详情列表:
  522. // GET /api/openapi-instance/v4/virtual-networks?instanceId=xxx&page=1&pageSize=100
  523. func (r *SRegion) GetInstanceNics(ctx context.Context, instanceId string) ([]SInstanceNic, error) {
  524. req := NewOpenApiInstanceActionRequest(r.RegionId, "/api/openapi-instance/v4/virtual-networks", nil)
  525. query := req.Base().GetQueryParams()
  526. query["instanceId"] = instanceId
  527. nets := make([]sDescribeVirtualNetworksResp, 0)
  528. if err := r.client.doList(ctx, req.Base(), &nets); err != nil {
  529. return nil, err
  530. }
  531. ret := make([]SInstanceNic, 0, len(nets))
  532. for i := range nets {
  533. n := nets[i]
  534. nic := SInstanceNic{
  535. Id: n.Id,
  536. PortId: n.Id,
  537. PortName: n.Name,
  538. PrivateIp: "",
  539. FipAddress: n.PublicIp,
  540. FipBandwidthSize: n.BandwidthSize,
  541. }
  542. nic.MacAddress = n.MacAddress
  543. nic.PublicIp = n.PublicIp
  544. for j := range n.SecurityGroupResps {
  545. sg := n.SecurityGroupResps[j]
  546. nic.SecurityGroups = append(nic.SecurityGroups, SSecurityGroupRef{
  547. Id: sg.Id,
  548. Name: sg.Name,
  549. })
  550. }
  551. for j := range n.FixedIpResps {
  552. ip := n.FixedIpResps[j]
  553. if nic.PrivateIp == "" && ip.IpAddress != "" {
  554. nic.PrivateIp = ip.IpAddress
  555. }
  556. if nic.NetworkId == "" && ip.SubnetId != "" {
  557. nic.NetworkId = ip.SubnetId
  558. }
  559. nic.FixedIpDetails = append(nic.FixedIpDetails, SFixedIpDetail{
  560. IpAddress: ip.IpAddress,
  561. IpVersion: fmt.Sprintf("%d", ip.IpVersion),
  562. SubnetId: ip.SubnetId,
  563. SubnetName: func() string {
  564. if ip.SubnetName != "" {
  565. return ip.SubnetName
  566. }
  567. return n.SubnetName
  568. }(),
  569. })
  570. }
  571. ret = append(ret, nic)
  572. }
  573. return ret, nil
  574. }
  575. type SInstanceBillInfo struct {
  576. AutoEndTime string `json:"autoEndTime"`
  577. PriceUnit string `json:"priceUnit"`
  578. ChargingMode int32 `json:"chargingMode"`
  579. RespDesc string `json:"respDesc"`
  580. InstanceId string `json:"instanceId"`
  581. AutoRenew string `json:"autoRenew"`
  582. EndTime string `json:"endTime"`
  583. }
  584. // GetInstanceBillInfo 查询实例计费信息:
  585. // POST /api/openapi-instance/v4/batch-query-price-info
  586. func (r *SRegion) GetInstanceBillInfo(ctx context.Context, instanceIds []string) ([]SInstanceBillInfo, error) {
  587. if len(instanceIds) == 0 {
  588. return []SInstanceBillInfo{}, nil
  589. }
  590. body := jsonutils.NewDict()
  591. body.Set("batchQueryPriceInfoBody", jsonutils.Marshal(map[string][]string{
  592. "instanceIds": instanceIds,
  593. }))
  594. req := NewOpenApiInstanceActionRequest(r.RegionId, "/api/openapi-instance/v4/batch-query-price-info", body)
  595. ret := make([]SInstanceBillInfo, 0)
  596. if err := r.client.doPost(ctx, req.Base(), &ret); err != nil {
  597. return nil, err
  598. }
  599. return ret, nil
  600. }
  601. // GetInsatnceBillInfo 保持兼容(历史拼写错误)
  602. func (r *SRegion) GetInsatnceBillInfo(ctx context.Context, instanceIds []string) ([]SInstanceBillInfo, error) {
  603. return r.GetInstanceBillInfo(ctx, instanceIds)
  604. }