yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcs/eip.go (about)

     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  
    15  package hcs
    16  
    17  import (
    18  	"fmt"
    19  	"net/url"
    20  	"strings"
    21  	"time"
    22  
    23  	"yunion.io/x/jsonutils"
    24  	"yunion.io/x/log"
    25  	"yunion.io/x/pkg/errors"
    26  
    27  	billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
    28  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    29  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    30  	"yunion.io/x/cloudmux/pkg/multicloud"
    31  )
    32  
    33  type TInternetChargeType string
    34  
    35  const (
    36  	InternetChargeByTraffic   = TInternetChargeType("traffic")
    37  	InternetChargeByBandwidth = TInternetChargeType("bandwidth")
    38  )
    39  
    40  type Bandwidth struct {
    41  	Id                  string         `json:"id"`
    42  	Name                string         `json:"name"`
    43  	Size                int64          `json:"size"`
    44  	ShareType           string         `json:"share_type"`
    45  	PublicipInfo        []PublicipInfo `json:"publicip_info"`
    46  	TenantId            string         `json:"tenant_id"`
    47  	BandwidthType       string         `json:"bandwidth_type"`
    48  	ChargeMode          string         `json:"charge_mode"`
    49  	BillingInfo         string         `json:"billing_info"`
    50  	EnterpriseProjectId string         `json:"enterprise_project_id"`
    51  }
    52  
    53  type PublicipInfo struct {
    54  	PublicipId      string `json:"publicip_id"`
    55  	PublicipAddress string `json:"publicip_address"`
    56  	PublicipType    string `json:"publicip_type"`
    57  	IPVersion       int64  `json:"ip_version"`
    58  }
    59  
    60  type SProfile struct {
    61  	UserId    string `json:"user_id"`
    62  	ProductId string `json:"product_id"`
    63  	RegionId  string `json:"region_id"`
    64  	OrderId   string `json:"order_id"`
    65  }
    66  
    67  type SEip struct {
    68  	multicloud.SEipBase
    69  	HcsTags
    70  
    71  	region *SRegion
    72  	port   *Port
    73  
    74  	Alias               string
    75  	Id                  string    `json:"id"`
    76  	Status              string    `json:"status"`
    77  	Profile             *SProfile `json:"profile,omitempty"`
    78  	Type                string    `json:"type"`
    79  	PublicIPAddress     string    `json:"public_ip_address"`
    80  	PrivateIPAddress    string    `json:"private_ip_address"`
    81  	TenantId            string    `json:"tenant_id"`
    82  	CreateTime          time.Time `json:"create_time"`
    83  	BandwidthId         string    `json:"bandwidth_id"`
    84  	BandwidthShareType  string    `json:"bandwidth_share_type"`
    85  	BandwidthSize       int64     `json:"bandwidth_size"`
    86  	BandwidthName       string    `json:"bandwidth_name"`
    87  	EnterpriseProjectId string    `json:"enterprise_project_id"`
    88  	IPVersion           int64     `json:"ip_version"`
    89  	PortId              string    `json:"port_id"`
    90  }
    91  
    92  func (self *SEip) GetId() string {
    93  	return self.Id
    94  }
    95  
    96  func (self *SEip) GetName() string {
    97  	if len(self.Alias) > 0 {
    98  		return self.Alias
    99  	}
   100  	return self.PublicIPAddress
   101  }
   102  
   103  func (self *SEip) GetGlobalId() string {
   104  	return self.Id
   105  }
   106  
   107  func (self *SEip) GetStatus() string {
   108  	// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090598.html
   109  	switch self.Status {
   110  	case "ACTIVE", "DOWN", "ELB":
   111  		return api.EIP_STATUS_READY
   112  	case "PENDING_CREATE", "NOTIFYING":
   113  		return api.EIP_STATUS_ALLOCATE
   114  	case "BINDING":
   115  		return api.EIP_STATUS_ALLOCATE
   116  	case "BIND_ERROR":
   117  		return api.EIP_STATUS_ALLOCATE_FAIL
   118  	case "PENDING_DELETE", "NOTIFY_DELETE":
   119  		return api.EIP_STATUS_DEALLOCATE
   120  	default:
   121  		return api.EIP_STATUS_UNKNOWN
   122  	}
   123  }
   124  
   125  func (self *SEip) Refresh() error {
   126  	if self.IsEmulated() {
   127  		return nil
   128  	}
   129  	new, err := self.region.GetEip(self.Id)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	return jsonutils.Update(self, new)
   134  }
   135  
   136  func (self *SEip) IsEmulated() bool {
   137  	return false
   138  }
   139  
   140  func (self *SEip) GetIpAddr() string {
   141  	return self.PublicIPAddress
   142  }
   143  
   144  func (self *SEip) GetMode() string {
   145  	return api.EIP_MODE_STANDALONE_EIP
   146  }
   147  
   148  func (self *SEip) GetPort() *Port {
   149  	if len(self.PortId) == 0 {
   150  		return nil
   151  	}
   152  
   153  	if self.port != nil {
   154  		return self.port
   155  	}
   156  
   157  	port, err := self.region.GetPort(self.PortId)
   158  	if err != nil {
   159  		return nil
   160  	}
   161  	self.port = port
   162  	return self.port
   163  }
   164  
   165  func (self *SEip) GetAssociationType() string {
   166  	if len(self.PortId) == 0 {
   167  		return ""
   168  	}
   169  	port, err := self.region.GetPort(self.PortId)
   170  	if err != nil {
   171  		log.Errorf("Get eip %s port %s error: %v", self.Id, self.PortId, err)
   172  		return ""
   173  	}
   174  
   175  	if strings.HasPrefix(port.DeviceOwner, "compute") {
   176  		return api.EIP_ASSOCIATE_TYPE_SERVER
   177  	}
   178  
   179  	switch port.DeviceOwner {
   180  	case "neutron:LOADBALANCER", "neutron:LOADBALANCERV2":
   181  		return api.EIP_ASSOCIATE_TYPE_LOADBALANCER
   182  	case "network:nat_gateway":
   183  		return api.EIP_ASSOCIATE_TYPE_NAT_GATEWAY
   184  	default:
   185  		return port.DeviceOwner
   186  	}
   187  }
   188  
   189  func (self *SEip) GetAssociationExternalId() string {
   190  	// network/0273a359d61847fc83405926c958c746/ext-floatingips?tenantId=0273a359d61847fc83405926c958c746&limit=2000
   191  	// 只能通过 port id 反查device id.
   192  	if len(self.PortId) > 0 {
   193  		port, _ := self.region.GetPort(self.PortId)
   194  		return port.DeviceId
   195  	}
   196  	return ""
   197  }
   198  
   199  func (self *SEip) GetBandwidth() int {
   200  	return int(self.BandwidthSize) // Mb
   201  }
   202  
   203  func (self *SEip) GetINetworkId() string {
   204  	return ""
   205  }
   206  
   207  func (self *SEip) GetInternetChargeType() string {
   208  	// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090603.html
   209  	bandwidth, err := self.region.GetEipBandwidth(self.BandwidthId)
   210  	if err != nil {
   211  		return api.EIP_CHARGE_TYPE_BY_TRAFFIC
   212  	}
   213  	if bandwidth.ChargeMode == "traffic" {
   214  		return api.EIP_CHARGE_TYPE_BY_TRAFFIC
   215  	}
   216  	return api.EIP_CHARGE_TYPE_BY_BANDWIDTH
   217  }
   218  
   219  func (self *SEip) GetBillingType() string {
   220  	return billing_api.BILLING_TYPE_POSTPAID
   221  }
   222  
   223  func (self *SEip) GetCreatedAt() time.Time {
   224  	return self.CreateTime
   225  }
   226  
   227  func (self *SEip) GetExpiredAt() time.Time {
   228  	return time.Time{}
   229  }
   230  
   231  func (self *SEip) Delete() error {
   232  	return self.region.DeallocateEIP(self.Id)
   233  }
   234  
   235  func (self *SEip) Associate(conf *cloudprovider.AssociateConfig) error {
   236  	portId, err := self.region.GetInstancePortId(conf.InstanceId)
   237  	if err != nil {
   238  		return err
   239  	}
   240  
   241  	if len(self.PortId) > 0 {
   242  		if self.PortId == portId {
   243  			return nil
   244  		}
   245  
   246  		return fmt.Errorf("eip %s aready associate with port %s", self.GetId(), self.PortId)
   247  	}
   248  
   249  	err = self.region.AssociateEipWithPortId(self.Id, portId)
   250  	if err != nil {
   251  		return err
   252  	}
   253  
   254  	err = cloudprovider.WaitStatusWithDelay(self, api.EIP_STATUS_READY, 10*time.Second, 10*time.Second, 180*time.Second)
   255  	return err
   256  }
   257  
   258  func (self *SEip) Dissociate() error {
   259  	err := self.region.DissociateEip(self.Id)
   260  	if err != nil {
   261  		return err
   262  	}
   263  	return cloudprovider.WaitStatus(self, api.EIP_STATUS_READY, 10*time.Second, 180*time.Second)
   264  }
   265  
   266  func (self *SEip) ChangeBandwidth(bw int) error {
   267  	return self.region.UpdateEipBandwidth(self.BandwidthId, bw)
   268  }
   269  
   270  func (self *SRegion) GetInstancePortId(instanceId string) (string, error) {
   271  	// 目前只绑定一个网卡
   272  	// todo: 还需要按照ports状态进行过滤
   273  	ports, err := self.GetPorts(instanceId)
   274  	if err != nil {
   275  		return "", err
   276  	}
   277  
   278  	if len(ports) == 0 {
   279  		return "", fmt.Errorf("AssociateEip instance %s port is empty", instanceId)
   280  	}
   281  
   282  	return ports[0].Id, nil
   283  }
   284  
   285  // https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090596.html
   286  func (self *SRegion) AllocateEIP(name string, bwMbps int, chargeType TInternetChargeType, bgpType string, projectId string) (*SEip, error) {
   287  	params := map[string]interface{}{
   288  		"bandwidth": map[string]interface{}{
   289  			"name":        name,
   290  			"size":        bwMbps,
   291  			"share_type":  "PER",
   292  			"charge_mode": chargeType,
   293  		},
   294  		"publicip": map[string]interface{}{
   295  			"type":       bgpType,
   296  			"ip_version": 4,
   297  			"alias":      name,
   298  		},
   299  	}
   300  	if len(projectId) > 0 {
   301  		params["enterprise_project_id"] = projectId
   302  	}
   303  	eip := &SEip{region: self}
   304  	return eip, self.vpcCreate("publicips", params, eip)
   305  }
   306  
   307  func (self *SRegion) GetEip(eipId string) (*SEip, error) {
   308  	eip := &SEip{region: self}
   309  	return eip, self.vpcGet("publicips/"+eipId, eip)
   310  }
   311  
   312  func (self *SRegion) DeallocateEIP(eipId string) error {
   313  	return self.vpcDelete("publicips/" + eipId)
   314  }
   315  
   316  func (self *SRegion) AssociateEip(eipId string, instanceId string) error {
   317  	portId, err := self.GetInstancePortId(instanceId)
   318  	if err != nil {
   319  		return err
   320  	}
   321  	return self.AssociateEipWithPortId(eipId, portId)
   322  }
   323  
   324  func (self *SRegion) AssociateEipWithPortId(eipId string, portId string) error {
   325  	params := map[string]interface{}{
   326  		"publicip": map[string]interface{}{
   327  			"port_id": portId,
   328  		},
   329  	}
   330  	return self.vpcUpdate("publicips/"+eipId, params)
   331  }
   332  
   333  func (self *SRegion) DissociateEip(eipId string) error {
   334  	return self.AssociateEipWithPortId(eipId, "")
   335  }
   336  
   337  func (self *SRegion) UpdateEipBandwidth(bandwidthId string, bw int) error {
   338  	params := map[string]interface{}{
   339  		"bandwidth": map[string]interface{}{
   340  			"size": bw,
   341  		},
   342  	}
   343  	return self.vpcUpdate("bandwidths/"+bandwidthId, params)
   344  }
   345  
   346  func (self *SRegion) GetEipBandwidth(id string) (*Bandwidth, error) {
   347  	ret := &Bandwidth{}
   348  	return ret, self.vpcGet("bandwidths/"+id, ret)
   349  }
   350  
   351  func (self *SEip) GetProjectId() string {
   352  	return self.EnterpriseProjectId
   353  }
   354  
   355  func (self *SRegion) GetEips(portId string, addrs []string) ([]SEip, error) {
   356  	query := url.Values{}
   357  	for _, addr := range addrs {
   358  		query.Add("public_ip_address", addr)
   359  	}
   360  	if len(portId) > 0 {
   361  		query.Set("port_id", portId)
   362  	}
   363  	eips := []SEip{}
   364  	return eips, self.vpcList("publicips", query, &eips)
   365  }
   366  
   367  type SEipType struct {
   368  	Id    string
   369  	Type  string
   370  	Name  string
   371  	Group string
   372  }
   373  
   374  func (self *SRegion) GetEipTypes() ([]SEipType, error) {
   375  	query := url.Values{}
   376  	ret := []SEipType{}
   377  	return ret, self.vpcList("publicip_types", query, &ret)
   378  }
   379  
   380  func (self *SRegion) GetIEips() ([]cloudprovider.ICloudEIP, error) {
   381  	eips, err := self.GetEips("", nil)
   382  	if err != nil {
   383  		return nil, err
   384  	}
   385  	ret := []cloudprovider.ICloudEIP{}
   386  	for i := 0; i < len(eips); i += 1 {
   387  		eips[i].region = self
   388  		ret = append(ret, &eips[i])
   389  	}
   390  	return ret, nil
   391  }
   392  
   393  func (self *SRegion) GetIEipById(id string) (cloudprovider.ICloudEIP, error) {
   394  	eip, err := self.GetEip(id)
   395  	if err != nil {
   396  		return nil, err
   397  	}
   398  	return eip, nil
   399  }
   400  
   401  func (self *SRegion) CreateEIP(eip *cloudprovider.SEip) (cloudprovider.ICloudEIP, error) {
   402  	var ctype TInternetChargeType
   403  	switch eip.ChargeType {
   404  	case api.EIP_CHARGE_TYPE_BY_TRAFFIC:
   405  		ctype = InternetChargeByTraffic
   406  	case api.EIP_CHARGE_TYPE_BY_BANDWIDTH:
   407  		ctype = InternetChargeByBandwidth
   408  	}
   409  
   410  	// todo: 如何避免hardcode。集成到cloudmeta服务中?
   411  	if len(eip.BGPType) == 0 {
   412  		types, err := self.GetEipTypes()
   413  		if err != nil {
   414  			return nil, errors.Wrapf(err, "GetEipTypes")
   415  		}
   416  		if len(types) > 0 {
   417  			eip.BGPType = types[0].Type
   418  		}
   419  	}
   420  
   421  	// 华为云EIP名字最大长度64
   422  	if len(eip.Name) > 64 {
   423  		eip.Name = eip.Name[:64]
   424  	}
   425  
   426  	ieip, err := self.AllocateEIP(eip.Name, eip.BandwidthMbps, ctype, eip.BGPType, eip.ProjectId)
   427  	ieip.region = self
   428  	if err != nil {
   429  		return nil, err
   430  	}
   431  
   432  	err = cloudprovider.WaitStatus(ieip, api.EIP_STATUS_READY, 5*time.Second, 60*time.Second)
   433  	return ieip, err
   434  }