region.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  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. "strconv"
  19. "strings"
  20. "time"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/pkg/errors"
  23. api "yunion.io/x/cloudmux/pkg/apis/compute"
  24. "yunion.io/x/cloudmux/pkg/cloudprovider"
  25. "yunion.io/x/cloudmux/pkg/multicloud"
  26. )
  27. // SOpenApiRegion 用于接收 OpenAPI 区域列表返回的单个区域结构
  28. type SRegion struct {
  29. cloudprovider.SFakeOnPremiseRegion
  30. multicloud.SRegion
  31. multicloud.SNoObjectStorageRegion
  32. client *SEcloudClient
  33. RegionId string `json:"regionId"`
  34. RegionCode string `json:"regionCode"`
  35. RegionName string `json:"regionName"`
  36. RegionArea string `json:"regionArea"`
  37. }
  38. func (r *SRegion) GetId() string {
  39. return r.RegionId
  40. }
  41. func (r *SRegion) GetName() string {
  42. return r.RegionName
  43. }
  44. func (r *SRegion) GetGlobalId() string {
  45. return fmt.Sprintf("%s/%s", r.client.GetAccessEnv(), r.RegionId)
  46. }
  47. func (r *SRegion) GetStatus() string {
  48. return api.CLOUD_REGION_STATUS_INSERVER
  49. }
  50. func (r *SRegion) Refresh() error {
  51. return nil
  52. }
  53. func (r *SRegion) IsEmulated() bool {
  54. return false
  55. }
  56. func (r *SRegion) GetI18n() cloudprovider.SModelI18nTable {
  57. table := cloudprovider.SModelI18nTable{}
  58. return table
  59. }
  60. // GetLatitude() float32
  61. // GetLongitude() float32
  62. func (r *SRegion) GetGeographicInfo() cloudprovider.SGeographicInfo {
  63. if info, ok := LatitudeAndLongitude[r.RegionId]; ok {
  64. return info
  65. }
  66. return cloudprovider.SGeographicInfo{}
  67. }
  68. func (r *SRegion) GetIZones() ([]cloudprovider.ICloudZone, error) {
  69. zones, err := r.GetZones()
  70. if err != nil {
  71. return nil, err
  72. }
  73. izones := make([]cloudprovider.ICloudZone, len(zones))
  74. for i := range zones {
  75. izones[i] = &zones[i]
  76. }
  77. return izones, nil
  78. }
  79. func (r *SRegion) GetZones() ([]SZone, error) {
  80. zones := make([]SZone, 0)
  81. req := NewOpenApiZoneRequest(r.RegionId, nil)
  82. if err := r.client.doList(context.Background(), req.Base(), &zones); err != nil {
  83. return nil, errors.Wrap(err, "GetZones")
  84. }
  85. for i := range zones {
  86. zones[i].region = r
  87. }
  88. return zones, nil
  89. }
  90. func (r *SRegion) GetVpcs() ([]SVpc, error) {
  91. req := NewOpenApiVpcRequest(r.RegionId, "/api/openapi-vpc/customer/v3/vpc", nil, nil)
  92. vpcs := make([]SVpc, 0)
  93. if err := r.client.doList(context.Background(), req.Base(), &vpcs); err != nil {
  94. return nil, err
  95. }
  96. for i := range vpcs {
  97. vpcs[i].region = r
  98. }
  99. return vpcs, nil
  100. }
  101. func (r *SRegion) GetVpc(id string) (*SVpc, error) {
  102. base := NewOpenApiVpcRequest(r.RegionId, fmt.Sprintf("/api/openapi-vpc/customer/v3/vpc/%s", id), nil, nil).Base()
  103. base.SetMethod("GET")
  104. resp, err := r.client.request(context.Background(), base)
  105. if err != nil {
  106. // 可能传入的是 routerId,再试详情接口 by routerId
  107. return r.getVpcByRouterId(id)
  108. }
  109. vpc := SVpc{}
  110. if err := resp.Unmarshal(&vpc); err != nil {
  111. return nil, errors.Wrap(err, "Unmarshal vpc")
  112. }
  113. vpc.region = r
  114. return &vpc, nil
  115. }
  116. func (r *SRegion) getVpcByRouterId(routerId string) (*SVpc, error) {
  117. base := NewOpenApiVpcRequest(r.RegionId, fmt.Sprintf("/api/openapi-vpc/customer/v3/vpc/router/%s", routerId), nil, nil).Base()
  118. base.SetMethod("GET")
  119. resp, err := r.client.request(context.Background(), base)
  120. if err != nil {
  121. return nil, err
  122. }
  123. vpc := SVpc{}
  124. if err := resp.Unmarshal(&vpc); err != nil {
  125. return nil, errors.Wrap(err, "Unmarshal vpc")
  126. }
  127. vpc.region = r
  128. return &vpc, nil
  129. }
  130. // CreateVpc 创建 VPC,OpenAPI POST /api/openapi-vpc/customer/v3/order/create/vpc。
  131. // name/networkName 须 5-22 位、字母开头;body 使用 vpcOrderCreateBody 包装以兼容网关。
  132. func (r *SRegion) CreateVpc(opts *cloudprovider.VpcCreateOptions) (*SVpc, error) {
  133. name := opts.NAME
  134. if name == "" {
  135. name = "vpc-default"
  136. }
  137. // 规范:必须以字母开头,长度 5-22(数字、字母、下划线)
  138. if name[0] < 'a' || name[0] > 'z' {
  139. if name[0] < 'A' || name[0] > 'Z' {
  140. name = "v" + name
  141. }
  142. }
  143. if len(name) < 5 {
  144. pad := 5 - len(name)
  145. if pad > 4 {
  146. pad = 4
  147. }
  148. name = name + "xxxx"[:pad]
  149. }
  150. if len(name) > 22 {
  151. name = name[:22]
  152. }
  153. cidr := opts.CIDR
  154. if cidr == "" {
  155. cidr = "192.168.0.0/16"
  156. }
  157. networkName := name + "-subnet1"
  158. if len(networkName) > 22 {
  159. networkName = name + "-s1"
  160. if len(networkName) > 22 {
  161. networkName = name[:20] + "-s1"
  162. }
  163. }
  164. poolID := regionIdToPoolId[r.RegionId]
  165. if poolID == "" {
  166. poolID = r.RegionId
  167. }
  168. inner := jsonutils.NewDict()
  169. inner.Set("name", jsonutils.NewString(name))
  170. inner.Set("cidr", jsonutils.NewString(cidr))
  171. inner.Set("networkName", jsonutils.NewString(networkName))
  172. inner.Set("region", jsonutils.NewString(poolID))
  173. inner.Set("specs", jsonutils.NewString("high"))
  174. inner.Set("networkTypeEnum", jsonutils.NewString("VM"))
  175. if opts.Desc != "" {
  176. inner.Set("description", jsonutils.NewString(opts.Desc))
  177. }
  178. body := jsonutils.NewDict()
  179. body.Set("vpcOrderCreateBody", inner)
  180. base := NewOpenApiVpcRequest(r.RegionId, "/api/openapi-vpc/customer/v3/order/create/vpc", nil, body).Base()
  181. base.SetMethod("POST")
  182. _, err := r.client.request(context.Background(), base)
  183. if err != nil {
  184. if strings.Contains(err.Error(), "该可用区暂无可用的网络资源") || strings.Contains(err.Error(), "THIS_AZ_MUST_AUTH") {
  185. return nil, errors.Wrap(err, "当前区域/可用区暂无可用网络资源或需开通权限,请尝试其他区域或联系移动云")
  186. }
  187. return nil, errors.Wrap(err, "CreateVpc")
  188. }
  189. // 创建为订购接口,返回 orderId;轮询列表按名称查找新 VPC
  190. for retry := 0; retry < 3; retry++ {
  191. if retry > 0 {
  192. time.Sleep(time.Duration(3+retry*2) * time.Second)
  193. }
  194. vpcs, err := r.GetVpcs()
  195. if err != nil {
  196. continue
  197. }
  198. for i := range vpcs {
  199. if vpcs[i].Name == name {
  200. vpcs[i].region = r
  201. return &vpcs[i], nil
  202. }
  203. }
  204. }
  205. return nil, errors.Wrap(errors.ErrNotFound, "created vpc not found in list yet")
  206. }
  207. // DeleteVpc 退订 VPC,OpenAPI POST /api/openapi-vpc/customer/v3/order/delete。
  208. // 参数 idOrRouterId 可为 vpc Id 或 routerId;若为 vpc Id 会先查详情取 routerId 再退订。
  209. func (r *SRegion) DeleteVpc(idOrRouterId string) error {
  210. routerId := idOrRouterId
  211. if vpc, err := r.GetVpc(idOrRouterId); err == nil {
  212. routerId = vpc.RouterId
  213. }
  214. commonBody := jsonutils.NewDict()
  215. commonBody.Set("resourceId", jsonutils.NewString(routerId))
  216. commonBody.Set("productType", jsonutils.NewString("router"))
  217. reqBody := jsonutils.NewDict()
  218. reqBody.Set("commonMopOrderDeleteVpcBody", commonBody)
  219. base := NewOpenApiVpcRequest(r.RegionId, "/api/openapi-vpc/customer/v3/order/delete", nil, reqBody).Base()
  220. base.SetMethod("POST")
  221. _, err := r.client.request(context.Background(), base)
  222. return errors.Wrap(err, "DeleteVpc")
  223. }
  224. // DeleteNetwork 删除 VPC 下网络(子网),与 ecloudsdkvpc DeleteNetwork 一致。
  225. func (r *SRegion) DeleteNetwork(networkId string) error {
  226. base := NewOpenApiVpcRequest(r.RegionId, "/api/openapi-vpc/customer/v3/network/"+networkId, nil, nil).Base()
  227. base.SetMethod("DELETE")
  228. _, err := r.client.request(context.Background(), base)
  229. return errors.Wrap(err, "DeleteNetwork")
  230. }
  231. // CreateNetwork 在 VPC 下创建网络(子网),与 ecloudsdkvpc CreateNetwork 一致。
  232. // networkName 须 5-22 位、字母开头;cidr 如 192.168.1.0/24。
  233. func (r *SRegion) CreateNetwork(routerId, regionPoolId, networkName, cidr string) (*SNetwork, error) {
  234. body := jsonutils.NewDict()
  235. inner := jsonutils.NewDict()
  236. inner.Set("routerId", jsonutils.NewString(routerId))
  237. inner.Set("networkName", jsonutils.NewString(networkName))
  238. inner.Set("networkTypeEnum", jsonutils.NewString("VM"))
  239. if regionPoolId != "" {
  240. inner.Set("availabilityZoneHints", jsonutils.NewString(regionPoolId))
  241. }
  242. subnet := jsonutils.NewDict()
  243. subnet.Set("cidr", jsonutils.NewString(cidr))
  244. subnet.Set("ipVersion", jsonutils.NewString("4"))
  245. inner.Set("subnets", jsonutils.NewArray(subnet))
  246. body.Set("createNetworkBody", inner)
  247. base := NewOpenApiVpcRequest(r.RegionId, "/api/openapi-vpc/customer/v3/network", nil, body).Base()
  248. base.SetMethod("POST")
  249. data, err := r.client.request(context.Background(), base)
  250. if err != nil {
  251. return nil, errors.Wrap(err, "CreateNetwork")
  252. }
  253. // 响应可能含 body(网络 id)或直接返回网络信息
  254. var networkId string
  255. if data.Contains("body") {
  256. networkId, _ = data.GetString("body")
  257. }
  258. if networkId == "" && data.Contains("id") {
  259. networkId, _ = data.GetString("id")
  260. }
  261. if networkId == "" {
  262. // 通过名称再查一次
  263. networks, listErr := r.GetNetworks(routerId, "")
  264. if listErr != nil {
  265. return nil, errors.Wrapf(listErr, "CreateNetwork succeeded but list networks failed")
  266. }
  267. for i := range networks {
  268. if networks[i].Name == networkName {
  269. return &networks[i], nil
  270. }
  271. }
  272. return nil, errors.Errorf("CreateNetwork succeeded but could not find created network by name %q", networkName)
  273. }
  274. return r.GetNetwork(networkId)
  275. }
  276. func (r *SRegion) GetIVpcs() ([]cloudprovider.ICloudVpc, error) {
  277. vpcs, err := r.GetVpcs()
  278. if err != nil {
  279. return nil, err
  280. }
  281. ivpcs := make([]cloudprovider.ICloudVpc, len(vpcs))
  282. for i := range vpcs {
  283. ivpcs[i] = &vpcs[i]
  284. }
  285. return ivpcs, nil
  286. }
  287. func (r *SRegion) GetIEips() ([]cloudprovider.ICloudEIP, error) {
  288. // 使用 OpenAPI EIP 公网 IP 列表(含带宽信息):
  289. // GET /api/openapi-eip/acl/v3/floatingip/listWithBw
  290. base := NewOpenApiEbsRequest(r.RegionId, "/api/openapi-eip/acl/v3/floatingip/listWithBw", nil, nil).Base()
  291. eips := make([]SEip, 0, 20)
  292. if err := r.client.doList(context.Background(), base, &eips); err != nil {
  293. return nil, err
  294. }
  295. ret := make([]cloudprovider.ICloudEIP, len(eips))
  296. for i := range eips {
  297. eips[i].region = r
  298. ret[i] = &eips[i]
  299. }
  300. return ret, nil
  301. }
  302. func (r *SRegion) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) {
  303. vpc, err := r.GetVpc(id)
  304. if err != nil {
  305. return nil, err
  306. }
  307. vpc.region = r
  308. return vpc, nil
  309. }
  310. func (r *SRegion) CreateIVpc(opts *cloudprovider.VpcCreateOptions) (cloudprovider.ICloudVpc, error) {
  311. vpc, err := r.CreateVpc(opts)
  312. if err != nil {
  313. return nil, err
  314. }
  315. return vpc, nil
  316. }
  317. func (r *SRegion) GetIZoneById(id string) (cloudprovider.ICloudZone, error) {
  318. zones, err := r.GetZones()
  319. if err != nil {
  320. return nil, err
  321. }
  322. for i := 0; i < len(zones); i += 1 {
  323. if zones[i].GetGlobalId() == id || zones[i].GetId() == id {
  324. return &zones[i], nil
  325. }
  326. }
  327. return nil, cloudprovider.ErrNotFound
  328. }
  329. func (r *SRegion) GetIEipById(id string) (cloudprovider.ICloudEIP, error) {
  330. return nil, cloudprovider.ErrNotImplemented
  331. }
  332. func (r *SRegion) GetIVMById(id string) (cloudprovider.ICloudVM, error) {
  333. vm, err := r.GetInstance(id)
  334. if err != nil {
  335. return nil, err
  336. }
  337. zones, err := r.GetZones()
  338. if err != nil {
  339. return nil, err
  340. }
  341. for i := range zones {
  342. if zones[i].ZoneId == vm.ZoneId {
  343. vm.host = &SHost{
  344. zone: &zones[i],
  345. }
  346. return vm, nil
  347. }
  348. }
  349. return nil, cloudprovider.ErrNotFound
  350. }
  351. func (r *SRegion) GetIDiskById(id string) (cloudprovider.ICloudDisk, error) {
  352. return r.GetDisk(id)
  353. }
  354. // ResizeDisk 扩容云盘,使用 OpenAPI /api/v2/volume/volume/change/ebs,与 ecloudsdkebs ChangeVolume 一致。
  355. // newSizeGB 为目标容量(GB),只做直通调用,参数合法性由上层调用方保证。
  356. func (r *SRegion) ResizeDisk(ctx context.Context, diskId string, newSizeGB int64) error {
  357. body := jsonutils.NewDict()
  358. body.Set("size", jsonutils.NewInt(newSizeGB))
  359. body.Set("changeType", jsonutils.NewString("CHANGE"))
  360. body.Set("volumeId", jsonutils.NewString(diskId))
  361. reqBody := jsonutils.NewDict()
  362. reqBody.Set("changeVolumeBody", body)
  363. base := NewOpenApiEbsRequest(r.RegionId, "/api/v2/volume/volume/change/ebs", nil, reqBody).Base()
  364. base.SetMethod("POST")
  365. _, err := r.client.request(ctx, base)
  366. return err
  367. }
  368. // PreDeleteVolume 退订/删除云盘(预删除),与 ecloudsdkebs PreDeleteResources 一致。
  369. func (r *SRegion) PreDeleteVolume(volumeId string) error {
  370. body := jsonutils.NewDict()
  371. body.Set("resourceId", jsonutils.NewString(volumeId))
  372. body.Set("resourceType", jsonutils.NewString("VOLUME"))
  373. reqBody := jsonutils.NewDict()
  374. reqBody.Set("preDeleteResourcesBody", body)
  375. base := NewOpenApiEbsRequest(r.RegionId, "/api/ebs/acl/v3/common/resource/preDelete", nil, reqBody).Base()
  376. base.SetMethod("POST")
  377. _, err := r.client.request(context.Background(), base)
  378. return errors.Wrap(err, "PreDeleteVolume")
  379. }
  380. // CreateEbsSnapshot 创建云盘快照,与 ecloudsdkebs CreateSnapshot 一致。
  381. func (r *SRegion) CreateEbsSnapshot(volumeId, name, description string) (string, error) {
  382. snapBody := jsonutils.NewDict()
  383. snapBody.Set("volumeId", jsonutils.NewString(volumeId))
  384. snapBody.Set("name", jsonutils.NewString(name))
  385. if description != "" {
  386. snapBody.Set("description", jsonutils.NewString(description))
  387. }
  388. reqBody := jsonutils.NewDict()
  389. reqBody.Set("createSnapshotBody", snapBody)
  390. base := NewOpenApiEbsRequest(r.RegionId, "/api/v2/volume/openApi/volumeSnapshot/create", nil, reqBody).Base()
  391. base.SetMethod("POST")
  392. data, err := r.client.request(context.Background(), base)
  393. if err != nil {
  394. return "", errors.Wrap(err, "CreateEbsSnapshot")
  395. }
  396. // 响应可能含 snapshotId 或 id
  397. if data.Contains("snapshotId") {
  398. id, _ := data.GetString("snapshotId")
  399. return id, nil
  400. }
  401. if data.Contains("id") {
  402. id, _ := data.GetString("id")
  403. return id, nil
  404. }
  405. return "", errors.Errorf("CreateEbsSnapshot response has no snapshotId/id: %s", data)
  406. }
  407. // DeleteEbsSnapshot 删除云盘快照,与 ecloudsdkebs Deletes 一致。
  408. func (r *SRegion) DeleteEbsSnapshot(snapshotId string) error {
  409. base := NewOpenApiEbsRequest(r.RegionId, "/api/ebs/acl/v3/openApi/volumeSnapshot/"+snapshotId, nil, nil).Base()
  410. base.SetMethod("DELETE")
  411. _, err := r.client.request(context.Background(), base)
  412. return errors.Wrap(err, "DeleteEbsSnapshot")
  413. }
  414. func (r *SRegion) GetIHosts() ([]cloudprovider.ICloudHost, error) {
  415. izones, err := r.GetIZones()
  416. if err != nil {
  417. return nil, err
  418. }
  419. iHosts := make([]cloudprovider.ICloudHost, 0, len(izones))
  420. for i := range izones {
  421. hosts, err := izones[i].GetIHosts()
  422. if err != nil {
  423. return nil, err
  424. }
  425. iHosts = append(iHosts, hosts...)
  426. }
  427. return iHosts, nil
  428. }
  429. func (r *SRegion) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
  430. hosts, err := r.GetIHosts()
  431. if err != nil {
  432. return nil, err
  433. }
  434. for i := range hosts {
  435. if hosts[i].GetGlobalId() == id {
  436. return hosts[i], nil
  437. }
  438. }
  439. return nil, cloudprovider.ErrNotFound
  440. }
  441. func (r *SRegion) GetIStorages() ([]cloudprovider.ICloudStorage, error) {
  442. iStores := make([]cloudprovider.ICloudStorage, 0)
  443. izones, err := r.GetIZones()
  444. if err != nil {
  445. return nil, err
  446. }
  447. for i := 0; i < len(izones); i += 1 {
  448. iZoneStores, err := izones[i].GetIStorages()
  449. if err != nil {
  450. return nil, err
  451. }
  452. iStores = append(iStores, iZoneStores...)
  453. }
  454. return iStores, nil
  455. }
  456. func (r *SRegion) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
  457. istores, err := r.GetIStorages()
  458. if err != nil {
  459. return nil, err
  460. }
  461. for i := range istores {
  462. if istores[i].GetGlobalId() == id {
  463. return istores[i], nil
  464. }
  465. }
  466. return nil, cloudprovider.ErrNotFound
  467. }
  468. func (r *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) {
  469. sc := r.getStoragecache()
  470. return []cloudprovider.ICloudStoragecache{sc}, nil
  471. }
  472. func (r *SRegion) GetIStoragecacheById(id string) (cloudprovider.ICloudStoragecache, error) {
  473. storageCache := r.getStoragecache()
  474. if storageCache.GetGlobalId() == id {
  475. return storageCache, nil
  476. }
  477. return nil, cloudprovider.ErrNotFound
  478. }
  479. type SPoolZoneInfo struct {
  480. ZoneId string `json:"zoneId"`
  481. ZoneName string `json:"zoneName"`
  482. ZoneCode string `json:"zoneCode"`
  483. }
  484. type SPoolInfo struct {
  485. ZoneInfo []SPoolZoneInfo `json:"zoneInfo"`
  486. PoolId string `json:"poolId"`
  487. PoolArea string `json:"poolArea"`
  488. ProductType string `json:"productType"`
  489. PoolName string `json:"poolName"`
  490. }
  491. type SPoolInfosRespBody struct {
  492. PoolList []SPoolInfo `json:"poolList"`
  493. }
  494. func (r *SRegion) GetPoolInfo(productType string) ([]SPoolInfo, error) {
  495. query := map[string]string{
  496. "productType": productType,
  497. }
  498. base := NewOpenApiEbsRequest(r.RegionId, "/api/ebs/acl/v3/mop/common/getPoolInfo", query, nil).Base()
  499. base.SetMethod("GET")
  500. data, err := r.client.request(context.Background(), base)
  501. if err != nil {
  502. return nil, err
  503. }
  504. resp := SPoolInfosRespBody{}
  505. if err := data.Unmarshal(&resp); err != nil {
  506. return nil, err
  507. }
  508. return resp.PoolList, nil
  509. }
  510. // GetStorages 返回当前区域(可选指定可用区)的可用存储列表。
  511. // 若 zoneCode 为空,则返回所有可用区;否则仅返回指定可用区的存储。
  512. func (r *SRegion) GetStorages(zoneCode string) ([]SStorage, error) {
  513. volumeConfig, err := r.GetVolumeConfig()
  514. if err != nil {
  515. return nil, err
  516. }
  517. ret := make([]SStorage, 0)
  518. for _, config := range volumeConfig {
  519. if config.Region == zoneCode {
  520. ret = append(ret, config)
  521. }
  522. }
  523. ret = append(ret, SStorage{
  524. StorageType: api.STORAGE_ECLOUD_LOCAL,
  525. Region: zoneCode,
  526. })
  527. return ret, nil
  528. }
  529. func (r *SRegion) GetVolumeConfig() ([]SStorage, error) {
  530. base := NewOpenApiEbsRequest(r.RegionId, "/api/ebs/customer/v3/volume/volumeType/list", nil, nil).Base()
  531. base.SetMethod("GET")
  532. data, err := r.client.request(context.Background(), base)
  533. if err != nil {
  534. return nil, errors.Wrap(err, "GetVolumeConfigs")
  535. }
  536. resp := []SStorage{}
  537. if err := data.Unmarshal(&resp); err != nil {
  538. return nil, errors.Wrap(err, "Unmarshal volume config")
  539. }
  540. return resp, nil
  541. }
  542. func (r *SRegion) GetProvider() string {
  543. return api.CLOUD_PROVIDER_ECLOUD
  544. }
  545. func (r *SRegion) GetCapabilities() []string {
  546. return r.client.GetCapabilities()
  547. }
  548. func (r *SRegion) GetClient() *SEcloudClient {
  549. return r.client
  550. }
  551. // NewPlaceholderRegion 返回仅持有 client 的 SRegion,用于无有效 region 时仍可执行 region-list(如 OpenAPI 失败导致列表为空)。
  552. func NewPlaceholderRegion(client *SEcloudClient, regionId string) *SRegion {
  553. return &SRegion{RegionId: regionId, client: client}
  554. }
  555. func (r *SRegion) FindZone(zoneCode string) (*SZone, error) {
  556. zones, err := r.GetZones()
  557. if err != nil {
  558. return nil, errors.Wrap(err, "unable to GetZones")
  559. }
  560. for i := range zones {
  561. if zones[i].ZoneCode == zoneCode {
  562. return &zones[i], nil
  563. }
  564. }
  565. return nil, cloudprovider.ErrNotFound
  566. }
  567. func (region *SRegion) GetIVMs() ([]cloudprovider.ICloudVM, error) {
  568. vms, err := region.GetInstances("", "")
  569. if err != nil {
  570. return nil, errors.Wrap(err, "GetVMs")
  571. }
  572. ivms := make([]cloudprovider.ICloudVM, len(vms))
  573. for i := range vms {
  574. ivms[i] = &vms[i]
  575. }
  576. return ivms, nil
  577. }
  578. func (r *SRegion) GetSecurityGroups() ([]SSecurityGroup, error) {
  579. // --- 安全组 OpenAPI(ecloudsdkvpc 路径,使用 console 主机)---
  580. query := map[string]string{
  581. "region": r.RegionId,
  582. }
  583. req := NewOpenApiVpcRequest(r.RegionId, "/api/openapi-vpc/customer/v3/SecurityGroup", query, nil)
  584. sgs := make([]SSecurityGroup, 0, 8)
  585. if err := r.client.doList(context.Background(), req.Base(), &sgs); err != nil {
  586. return nil, err
  587. }
  588. for i := range sgs {
  589. sgs[i].region = r
  590. }
  591. return sgs, nil
  592. }
  593. // GetSecurityGroup 按 ID 获取安全组,返回底层 SSecurityGroup 结构。
  594. func (r *SRegion) GetSecurityGroup(id string) (*SSecurityGroup, error) {
  595. base := NewOpenApiVpcRequest(r.RegionId, fmt.Sprintf("/api/openapi-vpc/customer/v3/SecurityGroup/%s", id), nil, nil).Base()
  596. base.SetMethod("GET")
  597. resp, err := r.client.request(context.Background(), base)
  598. if err != nil {
  599. return nil, err
  600. }
  601. sg := SSecurityGroup{}
  602. if err := resp.Unmarshal(&sg); err != nil {
  603. return nil, errors.Wrap(err, "Unmarshal security group")
  604. }
  605. sg.region = r
  606. return &sg, nil
  607. }
  608. func (r *SRegion) CreateSecurityGroup(opts *cloudprovider.SecurityGroupCreateInput) (*SSecurityGroup, error) {
  609. name := opts.Name
  610. if name == "" {
  611. name = "sg-default"
  612. }
  613. poolID := regionIdToPoolId[r.RegionId]
  614. if poolID == "" {
  615. poolID = r.RegionId
  616. }
  617. // SDK 发送的 Body 为 createSecurityGroupBody 内层,与 VPC 一致
  618. body := jsonutils.NewDict()
  619. body.Set("name", jsonutils.NewString(name))
  620. body.Set("region", jsonutils.NewString(poolID))
  621. body.Set("type", jsonutils.NewString("VM"))
  622. if opts.Desc != "" {
  623. body.Set("description", jsonutils.NewString(opts.Desc))
  624. }
  625. base := NewOpenApiVpcRequest(r.RegionId, "/api/openapi-vpc/customer/v3/SecurityGroup", nil, body).Base()
  626. base.SetMethod("POST")
  627. resp, err := r.client.request(context.Background(), base)
  628. if err != nil {
  629. return nil, errors.Wrap(err, "CreateSecurityGroup")
  630. }
  631. sg := SSecurityGroup{}
  632. if err := resp.Unmarshal(&sg); err != nil {
  633. return nil, errors.Wrap(err, "Unmarshal created security group")
  634. }
  635. sg.region = r
  636. return &sg, nil
  637. }
  638. func (r *SRegion) DeleteSecurityGroup(id string) error {
  639. base := NewOpenApiVpcRequest(r.RegionId, fmt.Sprintf("/api/openapi-vpc/customer/v3/SecurityGroup/%s", id), nil, nil).Base()
  640. base.SetMethod("DELETE")
  641. _, err := r.client.request(context.Background(), base)
  642. return errors.Wrap(err, "DeleteSecurityGroup")
  643. }
  644. func (r *SRegion) GetSecurityGroupRules(sgId string) ([]SSecurityGroupRule, error) {
  645. query := map[string]string{"securityGroupId": sgId, "page": "1", "pageSize": "100"}
  646. req := NewOpenApiVpcRequest(r.RegionId, "/api/openapi-vpc/customer/v3/SecurityGroupRule", query, nil)
  647. rules := make([]SSecurityGroupRule, 0)
  648. if err := r.client.doList(context.Background(), req.Base(), &rules); err != nil {
  649. return nil, err
  650. }
  651. for i := range rules {
  652. rules[i].region = r
  653. rules[i].SecgroupId = sgId
  654. }
  655. return rules, nil
  656. }
  657. func (r *SRegion) CreateSecurityGroupRule(sgId string, opts *cloudprovider.SecurityGroupRuleCreateOptions) (*SSecurityGroupRule, error) {
  658. body := jsonutils.NewDict()
  659. body.Set("securityGroupId", jsonutils.NewString(sgId))
  660. body.Set("remoteType", jsonutils.NewString("cidr"))
  661. body.Set("direction", jsonutils.NewString(string(opts.Direction)))
  662. proto := strings.ToUpper(opts.Protocol)
  663. if proto == "" || proto == "ANY" {
  664. proto = "ANY"
  665. }
  666. body.Set("protocol", jsonutils.NewString(proto))
  667. if opts.CIDR != "" {
  668. body.Set("remoteIpPrefix", jsonutils.NewString(opts.CIDR))
  669. }
  670. if opts.Desc != "" {
  671. body.Set("description", jsonutils.NewString(opts.Desc))
  672. }
  673. minPort, maxPort := parsePorts(opts.Ports)
  674. if minPort >= 0 {
  675. body.Set("minPortRange", jsonutils.NewInt(int64(minPort)))
  676. }
  677. if maxPort >= 0 {
  678. body.Set("maxPortRange", jsonutils.NewInt(int64(maxPort)))
  679. }
  680. base := NewOpenApiVpcRequest(r.RegionId, "/api/openapi-vpc/customer/v3/SecurityGroupRule", nil, body).Base()
  681. base.SetMethod("POST")
  682. resp, err := r.client.request(context.Background(), base)
  683. if err != nil {
  684. return nil, errors.Wrap(err, "CreateSecurityGroupRule")
  685. }
  686. rule := SSecurityGroupRule{}
  687. if err := resp.Unmarshal(&rule); err != nil {
  688. return nil, errors.Wrap(err, "Unmarshal created rule")
  689. }
  690. rule.region = r
  691. rule.SecgroupId = sgId
  692. return &rule, nil
  693. }
  694. func (r *SRegion) DeleteSecurityGroupRule(ruleId string) error {
  695. base := NewOpenApiVpcRequest(r.RegionId, fmt.Sprintf("/api/openapi-vpc/customer/v3/SecurityGroupRule/%s", ruleId), nil, nil).Base()
  696. base.SetMethod("DELETE")
  697. _, err := r.client.request(context.Background(), base)
  698. return errors.Wrap(err, "DeleteSecurityGroupRule")
  699. }
  700. func (r *SRegion) UpdatePortSecurityGroups(portId string, securityGroupIds []string) error {
  701. body := jsonutils.NewDict()
  702. body.Set("id", jsonutils.NewString(portId))
  703. arr := jsonutils.NewArray()
  704. for _, id := range securityGroupIds {
  705. arr.Add(jsonutils.NewString(id))
  706. }
  707. body.Set("securityGroupIds", arr)
  708. reqBody := jsonutils.NewDict()
  709. reqBody.Set("updatePortSecurityGroupsBody", body)
  710. base := NewOpenApiVpcRequest(r.RegionId, "/api/openapi-vpc/customer/v3/port/portSecurityGroups", nil, reqBody).Base()
  711. base.SetMethod("PUT")
  712. _, err := r.client.request(context.Background(), base)
  713. return errors.Wrap(err, "UpdatePortSecurityGroups")
  714. }
  715. func (r *SRegion) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, error) {
  716. sgs, err := r.GetSecurityGroups()
  717. if err != nil {
  718. return nil, err
  719. }
  720. ret := make([]cloudprovider.ICloudSecurityGroup, len(sgs))
  721. for i := range sgs {
  722. ret[i] = &sgs[i]
  723. }
  724. return ret, nil
  725. }
  726. func (r *SRegion) GetISecurityGroupById(id string) (cloudprovider.ICloudSecurityGroup, error) {
  727. sg, err := r.GetSecurityGroup(id)
  728. if err != nil {
  729. return nil, err
  730. }
  731. return sg, nil
  732. }
  733. func (r *SRegion) CreateISecurityGroup(opts *cloudprovider.SecurityGroupCreateInput) (cloudprovider.ICloudSecurityGroup, error) {
  734. sg, err := r.CreateSecurityGroup(opts)
  735. if err != nil {
  736. return nil, err
  737. }
  738. return sg, nil
  739. }
  740. // parsePorts 解析 "80" 或 "80-443" 为 min, max;无法解析返回 -1,-1。
  741. func parsePorts(ports string) (minPort, maxPort int32) {
  742. if ports == "" {
  743. return -1, -1
  744. }
  745. parts := strings.Split(ports, "-")
  746. if len(parts) == 1 {
  747. p, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 32)
  748. if err != nil {
  749. return -1, -1
  750. }
  751. return int32(p), int32(p)
  752. }
  753. if len(parts) == 2 {
  754. p1, e1 := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 32)
  755. p2, e2 := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 32)
  756. if e1 != nil || e2 != nil {
  757. return -1, -1
  758. }
  759. return int32(p1), int32(p2)
  760. }
  761. return -1, -1
  762. }