eip.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  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 huawei
  15. import (
  16. "fmt"
  17. "net/url"
  18. "strings"
  19. "time"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/pkg/errors"
  22. billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
  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. type SEipAddress struct {
  28. region *SRegion
  29. multicloud.SEipBase
  30. HuaweiTags
  31. Alias string
  32. Id string
  33. Status string
  34. Type string
  35. PublicIPAddress string
  36. CreateTime time.Time
  37. Bandwidth struct {
  38. Id string
  39. Size int
  40. ShareType string
  41. ChargeMode string
  42. Name string
  43. }
  44. BillingInfo string
  45. EnterpriseProjectId string
  46. AssociateInstanceType string
  47. AssociateInstanceId string
  48. IPVersion int64
  49. PortId string
  50. }
  51. func (self *SEipAddress) GetId() string {
  52. return self.Id
  53. }
  54. func (self *SEipAddress) GetName() string {
  55. if len(self.Alias) > 0 {
  56. return self.Alias
  57. }
  58. return self.PublicIPAddress
  59. }
  60. func (self *SEipAddress) GetGlobalId() string {
  61. return self.Id
  62. }
  63. func (self *SEipAddress) GetStatus() string {
  64. switch self.Status {
  65. case "ACTIVE", "DOWN", "ELB":
  66. return api.EIP_STATUS_READY
  67. case "PENDING_CREATE", "NOTIFYING":
  68. return api.EIP_STATUS_ALLOCATE
  69. case "BINDING":
  70. return api.EIP_STATUS_ALLOCATE
  71. case "BIND_ERROR":
  72. return api.EIP_STATUS_ALLOCATE_FAIL
  73. case "PENDING_DELETE", "NOTIFY_DELETE":
  74. return api.EIP_STATUS_DEALLOCATE
  75. default:
  76. return api.EIP_STATUS_UNKNOWN
  77. }
  78. }
  79. func (self *SEipAddress) Refresh() error {
  80. eip, err := self.region.GetEip(self.Id)
  81. if err != nil {
  82. return err
  83. }
  84. return jsonutils.Update(self, eip)
  85. }
  86. func (self *SEipAddress) GetIpAddr() string {
  87. return self.PublicIPAddress
  88. }
  89. func (self *SEipAddress) GetMode() string {
  90. return api.EIP_MODE_STANDALONE_EIP
  91. }
  92. func (self *SEipAddress) GetAssociationType() string {
  93. switch self.AssociateInstanceType {
  94. case "ELB", "ELBV1":
  95. return api.EIP_ASSOCIATE_TYPE_LOADBALANCER
  96. case "NATGW":
  97. return api.EIP_ASSOCIATE_TYPE_NAT_GATEWAY
  98. case "PORT":
  99. return api.EIP_ASSOCIATE_TYPE_SERVER
  100. default:
  101. return strings.ToLower(self.AssociateInstanceType)
  102. }
  103. }
  104. func (self *SEipAddress) GetAssociationExternalId() string {
  105. if self.AssociateInstanceType == "PORT" {
  106. port, err := self.region.GetPort(self.AssociateInstanceId)
  107. if err == nil {
  108. return port.DeviceID
  109. }
  110. }
  111. return self.AssociateInstanceId
  112. }
  113. func (self *SEipAddress) GetBandwidth() int {
  114. return self.Bandwidth.Size
  115. }
  116. func (self *SEipAddress) GetInternetChargeType() string {
  117. if self.Bandwidth.ChargeMode == "traffic" {
  118. return api.EIP_CHARGE_TYPE_BY_TRAFFIC
  119. }
  120. return api.EIP_CHARGE_TYPE_BY_BANDWIDTH
  121. }
  122. func (self *SEipAddress) GetBillingType() string {
  123. if len(self.BillingInfo) > 0 {
  124. return billing_api.BILLING_TYPE_PREPAID
  125. }
  126. return billing_api.BILLING_TYPE_POSTPAID
  127. }
  128. func (self *SEipAddress) GetCreatedAt() time.Time {
  129. return self.CreateTime
  130. }
  131. func (self *SEipAddress) GetExpiredAt() time.Time {
  132. return time.Time{}
  133. }
  134. func (self *SEipAddress) Delete() error {
  135. if self.GetBillingType() == billing_api.BILLING_TYPE_PREPAID {
  136. return self.region.CancelResourcesSubscription([]string{self.Id})
  137. }
  138. return self.region.DeallocateEIP(self.Id)
  139. }
  140. func (self *SEipAddress) Associate(opts *cloudprovider.AssociateConfig) error {
  141. switch opts.AssociateType {
  142. case api.EIP_ASSOCIATE_TYPE_SERVER:
  143. portId, err := self.region.GetInstancePortId(opts.InstanceId)
  144. if err != nil {
  145. return errors.Wrapf(err, "GetInstancePortId")
  146. }
  147. if len(self.PortId) > 0 {
  148. if self.PortId == portId {
  149. return nil
  150. }
  151. return fmt.Errorf("eip %s aready associate with port %s", self.GetId(), self.PortId)
  152. }
  153. err = self.region.AssociateEip(self.Id, portId, "PORT")
  154. if err != nil {
  155. return err
  156. }
  157. case api.EIP_ASSOCIATE_TYPE_LOADBALANCER:
  158. err := self.region.AssociateEip(self.Id, opts.InstanceId, "ELB")
  159. if err != nil {
  160. return err
  161. }
  162. case api.EIP_ASSOCIATE_TYPE_NAT_GATEWAY:
  163. err := self.region.AssociateEip(self.Id, opts.InstanceId, "NATGW")
  164. if err != nil {
  165. return err
  166. }
  167. default:
  168. return errors.Wrapf(cloudprovider.ErrNotSupported, "associate type %s", opts.AssociateType)
  169. }
  170. err := cloudprovider.WaitStatusWithDelay(self, api.EIP_STATUS_READY, 10*time.Second, 10*time.Second, 180*time.Second)
  171. return err
  172. }
  173. func (self *SEipAddress) Dissociate() error {
  174. err := self.region.DissociateEip(self.Id)
  175. if err != nil {
  176. return err
  177. }
  178. return cloudprovider.WaitStatus(self, api.EIP_STATUS_READY, 10*time.Second, 180*time.Second)
  179. }
  180. func (self *SEipAddress) ChangeBandwidth(bw int) error {
  181. return self.region.UpdateEipBandwidth(self.Bandwidth.Id, bw)
  182. }
  183. func (self *SRegion) GetInstancePortId(instanceId string) (string, error) {
  184. // 目前只绑定一个网卡
  185. // todo: 还需要按照ports状态进行过滤
  186. ports, err := self.GetPorts(instanceId)
  187. if err != nil {
  188. return "", err
  189. }
  190. if len(ports) == 0 {
  191. return "", fmt.Errorf("AssociateEip instance %s port is empty", instanceId)
  192. }
  193. return ports[0].ID, nil
  194. }
  195. // https://console.huaweicloud.com/apiexplorer/#/openapi/EIP/doc?version=v2&api=CreatePublicip
  196. func (self *SRegion) AllocateEIP(opts *cloudprovider.SEip) (*SEipAddress, error) {
  197. if len(opts.BGPType) == 0 {
  198. switch self.GetId() {
  199. case "cn-north-1", "cn-east-2", "cn-south-1":
  200. opts.BGPType = "5_sbgp"
  201. case "cn-northeast-1":
  202. opts.BGPType = "5_telcom"
  203. case "cn-north-4", "ap-southeast-1", "ap-southeast-2", "eu-west-0":
  204. opts.BGPType = "5_bgp"
  205. case "cn-southwest-2":
  206. opts.BGPType = "5_sbgp"
  207. default:
  208. opts.BGPType = "5_bgp"
  209. }
  210. }
  211. // 华为云EIP名字最大长度64
  212. if len(opts.Name) > 64 {
  213. opts.Name = opts.Name[:64]
  214. }
  215. tags := []string{}
  216. for k, v := range opts.Tags {
  217. tags = append(tags, fmt.Sprintf("%s*%s", k, v))
  218. }
  219. params := map[string]interface{}{
  220. "bandwidth": map[string]interface{}{
  221. "name": opts.Name,
  222. "size": opts.BandwidthMbps,
  223. "share_type": "PER",
  224. "charge_mode": opts.ChargeType,
  225. },
  226. "publicip": map[string]interface{}{
  227. "type": opts.BGPType,
  228. "ip_version": 4,
  229. "alias": opts.Name,
  230. "tags": tags,
  231. },
  232. }
  233. if len(opts.ProjectId) > 0 {
  234. params["enterprise_project_id"] = opts.ProjectId
  235. }
  236. resp, err := self.post(SERVICE_VPC, "publicips", params)
  237. if err != nil {
  238. return nil, err
  239. }
  240. eip := &SEipAddress{region: self}
  241. return eip, resp.Unmarshal(eip, "publicip")
  242. }
  243. // https://console.huaweicloud.com/apiexplorer/#/openapi/EIP/doc?version=v3&api=ShowPublicip
  244. func (self *SRegion) GetEip(eipId string) (*SEipAddress, error) {
  245. resp, err := self.list(SERVICE_VPC_V3, "eip/publicips/"+eipId, nil)
  246. if err != nil {
  247. return nil, err
  248. }
  249. eip := &SEipAddress{region: self}
  250. return eip, resp.Unmarshal(eip, "publicip")
  251. }
  252. // https://console.huaweicloud.com/apiexplorer/#/openapi/EIP/doc?version=v2&api=DeletePublicip
  253. func (self *SRegion) DeallocateEIP(eipId string) error {
  254. _, err := self.delete(SERVICE_VPC, "publicips/"+eipId)
  255. return err
  256. }
  257. // https://console.huaweicloud.com/apiexplorer/#/openapi/EIP/doc?version=v3&api=AssociatePublicips
  258. func (self *SRegion) AssociateEip(eipId string, associateId, associateType string) error {
  259. params := map[string]interface{}{
  260. "publicip": map[string]interface{}{
  261. "associate_instance_id": associateId,
  262. "associate_instance_type": associateType,
  263. },
  264. }
  265. res := fmt.Sprintf("eip/publicips/%s/associate-instance", eipId)
  266. _, err := self.post(SERVICE_VPC_V3, res, params)
  267. return err
  268. }
  269. // https://console.huaweicloud.com/apiexplorer/#/openapi/EIP/doc?version=v3&api=DisassociatePublicips
  270. func (self *SRegion) DissociateEip(eipId string) error {
  271. res := fmt.Sprintf("eip/publicips/%s/disassociate-instance", eipId)
  272. _, err := self.post(SERVICE_VPC_V3, res, nil)
  273. return err
  274. }
  275. // https://console.huaweicloud.com/apiexplorer/#/openapi/EIP/doc?version=v2&api=UpdateBandwidth
  276. func (self *SRegion) UpdateEipBandwidth(bandwidthId string, bw int) error {
  277. params := map[string]interface{}{
  278. "bandwidth": map[string]interface{}{
  279. "size": bw,
  280. },
  281. }
  282. _, err := self.put(SERVICE_VPC, "bandwidths/"+bandwidthId, params)
  283. return err
  284. }
  285. func (self *SEipAddress) GetProjectId() string {
  286. return self.EnterpriseProjectId
  287. }
  288. // https://console.huaweicloud.com/apiexplorer/#/openapi/EIP/doc?version=v3&api=ListPublicips
  289. func (self *SRegion) GetEips(portId string, addrs []string) ([]SEipAddress, error) {
  290. query := url.Values{}
  291. for _, addr := range addrs {
  292. query.Add("public_ip_address", addr)
  293. }
  294. if len(portId) > 0 {
  295. query.Set("vnic.port_id", portId)
  296. }
  297. query.Set("ip_version", "4")
  298. eips := []SEipAddress{}
  299. for {
  300. resp, err := self.list(SERVICE_VPC_V3, "eip/publicips", query)
  301. if err != nil {
  302. return nil, err
  303. }
  304. part := struct {
  305. Publicips []SEipAddress
  306. PageInfo sPageInfo
  307. }{}
  308. err = resp.Unmarshal(&part)
  309. if err != nil {
  310. return nil, err
  311. }
  312. eips = append(eips, part.Publicips...)
  313. if len(part.Publicips) == 0 || len(part.PageInfo.NextMarker) == 0 {
  314. break
  315. }
  316. query.Set("marker", part.PageInfo.NextMarker)
  317. }
  318. for i := range eips {
  319. eips[i].region = self
  320. }
  321. return eips, nil
  322. }
  323. // https://console.huaweicloud.com/apiexplorer/#/openapi/EIP/doc?version=v2&api=DeletePublicipTag
  324. func (self *SRegion) DeletePublicipTag(eipId string, key string) error {
  325. res := fmt.Sprintf("publicips/%s/tags/%s", eipId, key)
  326. _, err := self.delete(SERVICE_VPC_V2_0, res)
  327. return err
  328. }
  329. // https://console.huaweicloud.com/apiexplorer/#/openapi/EIP/doc?version=v2&api=CreatePublicipTag
  330. func (self *SRegion) CreatePublicipTag(eipId string, tags map[string]string) error {
  331. params := map[string]interface{}{
  332. "action": "create",
  333. }
  334. add := []map[string]string{}
  335. for k, v := range tags {
  336. add = append(add, map[string]string{"key": k, "value": v})
  337. }
  338. params["tags"] = add
  339. res := fmt.Sprintf("publicips/%s/tags/action", eipId)
  340. _, err := self.post(SERVICE_VPC_V2_0, res, params)
  341. return err
  342. }
  343. func (self *SRegion) setEipTags(id string, existedTags, tags map[string]string, replace bool) error {
  344. deleteTagsKey := []string{}
  345. for k := range existedTags {
  346. if replace {
  347. deleteTagsKey = append(deleteTagsKey, k)
  348. } else {
  349. if _, ok := tags[k]; ok {
  350. deleteTagsKey = append(deleteTagsKey, k)
  351. }
  352. }
  353. }
  354. if len(deleteTagsKey) > 0 {
  355. for _, k := range deleteTagsKey {
  356. err := self.DeletePublicipTag(id, k)
  357. if err != nil {
  358. return errors.Wrapf(err, "remove tags")
  359. }
  360. }
  361. }
  362. if len(tags) > 0 {
  363. err := self.CreatePublicipTag(id, tags)
  364. if err != nil {
  365. return errors.Wrapf(err, "add tags")
  366. }
  367. }
  368. return nil
  369. }
  370. func (self *SEipAddress) SetTags(tags map[string]string, replace bool) error {
  371. existedTags, err := self.GetTags()
  372. if err != nil {
  373. return errors.Wrap(err, "self.GetTags()")
  374. }
  375. return self.region.setEipTags(self.Id, existedTags, tags, replace)
  376. }