yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aliyun/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 aliyun
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"time"
    21  
    22  	"yunion.io/x/jsonutils"
    23  	"yunion.io/x/log"
    24  	"yunion.io/x/pkg/errors"
    25  	"yunion.io/x/pkg/utils"
    26  
    27  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    28  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    29  	"yunion.io/x/cloudmux/pkg/multicloud"
    30  )
    31  
    32  type TInternetChargeType string
    33  
    34  const (
    35  	InternetChargeByTraffic   = TInternetChargeType("PayByTraffic")
    36  	InternetChargeByBandwidth = TInternetChargeType("PayByBandwidth")
    37  )
    38  
    39  const (
    40  	EIP_STATUS_ASSOCIATING   = "Associating"
    41  	EIP_STATUS_UNASSOCIATING = "Unassociating"
    42  	EIP_STATUS_INUSE         = "InUse"
    43  	EIP_STATUS_AVAILABLE     = "Available"
    44  
    45  	EIP_OPERATION_LOCK_FINANCIAL = "financial"
    46  	EIP_OPERATION_LOCK_SECURITY  = "security"
    47  
    48  	EIP_INSTANCE_TYPE_ECS   = "EcsInstance" // (默认值):VPC类型的ECS实例
    49  	EIP_INTANNCE_TYPE_SLB   = "SlbInstance" // :VPC类型的SLB实例
    50  	EIP_INSTANCE_TYPE_NAT   = "Nat"         // :NAT网关
    51  	EIP_INSTANCE_TYPE_HAVIP = "HaVip"       // :HAVIP
    52  )
    53  
    54  /*
    55  {
    56  	"AllocationId":"eip-2zeddtan63ou44dtyt9s3",
    57  	"AllocationTime":"2019-02-23T06:48:36Z",
    58  	"Bandwidth":"100",
    59  	"ChargeType":"PostPaid",
    60  	"ExpiredTime":"",
    61  	"InstanceId":"",
    62  	"InstanceType":"",
    63  	"InternetChargeType":"PayByTraffic",
    64  	"IpAddress":"39.105.131.32",
    65  	"OperationLocks":{"LockReason":[]},
    66  	"RegionId":"cn-beijing",
    67  	"Status":"Available"
    68  }
    69  */
    70  
    71  type SEipAddress struct {
    72  	region *SRegion
    73  	multicloud.SEipBase
    74  	AliyunTags
    75  
    76  	Name         string
    77  	AllocationId string
    78  
    79  	InternetChargeType TInternetChargeType
    80  
    81  	IpAddress string
    82  	Status    string
    83  
    84  	InstanceType string
    85  	InstanceId   string
    86  	Bandwidth    int /* Mbps */
    87  
    88  	BusinessStatus     string
    89  	AllocationTime     time.Time
    90  	DeletionProtection bool
    91  	Descritpion        string
    92  	ISP                string
    93  	Mode               string
    94  	Netmode            string
    95  
    96  	OperationLocks string
    97  
    98  	ChargeType      TChargeType
    99  	ExpiredTime     time.Time
   100  	ResourceGroupId string
   101  }
   102  
   103  func (self *SEipAddress) GetId() string {
   104  	return self.AllocationId
   105  }
   106  
   107  func (self *SEipAddress) GetName() string {
   108  	if len(self.Name) > 0 {
   109  		return self.Name
   110  	}
   111  	return self.IpAddress
   112  }
   113  
   114  func (self *SEipAddress) GetGlobalId() string {
   115  	return self.AllocationId
   116  }
   117  
   118  func (self *SEipAddress) GetStatus() string {
   119  	switch self.Status {
   120  	case EIP_STATUS_AVAILABLE, EIP_STATUS_INUSE:
   121  		return api.EIP_STATUS_READY
   122  	case EIP_STATUS_ASSOCIATING:
   123  		return api.EIP_STATUS_ASSOCIATE
   124  	case EIP_STATUS_UNASSOCIATING:
   125  		return api.EIP_STATUS_DISSOCIATE
   126  	default:
   127  		return api.EIP_STATUS_UNKNOWN
   128  	}
   129  }
   130  
   131  func (self *SEipAddress) Refresh() error {
   132  	if self.IsEmulated() {
   133  		return nil
   134  	}
   135  	new, err := self.region.GetEip(self.AllocationId)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	return jsonutils.Update(self, new)
   140  }
   141  
   142  func (self *SEipAddress) IsEmulated() bool {
   143  	if self.AllocationId == self.InstanceId {
   144  		// fixed Public IP
   145  		return true
   146  	} else {
   147  		return false
   148  	}
   149  }
   150  
   151  func (self *SEipAddress) GetIpAddr() string {
   152  	return self.IpAddress
   153  }
   154  
   155  func (self *SEipAddress) GetMode() string {
   156  	if self.InstanceId == self.AllocationId {
   157  		return api.EIP_MODE_INSTANCE_PUBLICIP
   158  	} else {
   159  		return api.EIP_MODE_STANDALONE_EIP
   160  	}
   161  }
   162  
   163  func (self *SEipAddress) GetAssociationType() string {
   164  	switch self.InstanceType {
   165  	case EIP_INSTANCE_TYPE_ECS, "NetworkInterface":
   166  		return api.EIP_ASSOCIATE_TYPE_SERVER
   167  	case EIP_INSTANCE_TYPE_NAT:
   168  		return api.EIP_ASSOCIATE_TYPE_NAT_GATEWAY
   169  	case EIP_INTANNCE_TYPE_SLB:
   170  		return api.EIP_ASSOCIATE_TYPE_LOADBALANCER
   171  	default:
   172  		return self.InstanceType
   173  	}
   174  }
   175  
   176  func (self *SEipAddress) GetAssociationExternalId() string {
   177  	return self.InstanceId
   178  }
   179  
   180  func (self *SEipAddress) GetBillingType() string {
   181  	return convertChargeType(self.ChargeType)
   182  }
   183  
   184  func (self *SEipAddress) GetCreatedAt() time.Time {
   185  	return self.AllocationTime
   186  }
   187  
   188  func (self *SEipAddress) GetExpiredAt() time.Time {
   189  	return convertExpiredAt(self.ExpiredTime)
   190  }
   191  
   192  func (self *SEipAddress) Delete() error {
   193  	return self.region.DeallocateEIP(self.AllocationId)
   194  }
   195  
   196  func (self *SEipAddress) GetBandwidth() int {
   197  	return self.Bandwidth
   198  }
   199  
   200  func (self *SEipAddress) GetINetworkId() string {
   201  	return ""
   202  }
   203  
   204  func (self *SEipAddress) GetInternetChargeType() string {
   205  	switch self.InternetChargeType {
   206  	case InternetChargeByTraffic:
   207  		return api.EIP_CHARGE_TYPE_BY_TRAFFIC
   208  	case InternetChargeByBandwidth:
   209  		return api.EIP_CHARGE_TYPE_BY_BANDWIDTH
   210  	default:
   211  		return api.EIP_CHARGE_TYPE_BY_TRAFFIC
   212  	}
   213  }
   214  
   215  func (self *SEipAddress) Associate(conf *cloudprovider.AssociateConfig) error {
   216  	err := cloudprovider.Wait(20*time.Second, 60*time.Second, func() (bool, error) {
   217  		err := self.region.AssociateEip(self.AllocationId, conf.InstanceId)
   218  		if err != nil {
   219  			if isError(err, "IncorrectInstanceStatus") {
   220  				return false, nil
   221  			}
   222  			return false, errors.Wrap(err, "region.AssociateEip")
   223  		}
   224  		return true, nil
   225  	})
   226  	err = cloudprovider.WaitStatus(self, api.EIP_STATUS_READY, 10*time.Second, 180*time.Second)
   227  	return err
   228  }
   229  
   230  func (self *SEipAddress) Dissociate() error {
   231  	err := self.region.DissociateEip(self.AllocationId, self.InstanceId)
   232  	if err != nil {
   233  		return err
   234  	}
   235  	err = cloudprovider.WaitStatus(self, api.EIP_STATUS_READY, 10*time.Second, 180*time.Second)
   236  	return err
   237  }
   238  
   239  func (self *SEipAddress) ChangeBandwidth(bw int) error {
   240  	return self.region.UpdateEipBandwidth(self.AllocationId, bw)
   241  }
   242  
   243  func (region *SRegion) GetEips(eipId string, associatedId, addr string, offset int, limit int) ([]SEipAddress, int, error) {
   244  	if limit > 50 || limit <= 0 {
   245  		limit = 50
   246  	}
   247  
   248  	params := make(map[string]string)
   249  	params["RegionId"] = region.RegionId
   250  	params["PageSize"] = fmt.Sprintf("%d", limit)
   251  	params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1)
   252  	if len(addr) > 0 {
   253  		params["EipAddress"] = addr
   254  	}
   255  
   256  	if len(eipId) > 0 {
   257  		params["AllocationId"] = eipId
   258  	}
   259  
   260  	if len(associatedId) > 0 {
   261  		params["AssociatedInstanceId"] = associatedId
   262  		for prefix, instanceType := range map[string]string{"i-": "EcsInstance", "ngw-": "Nat", "lb-": "SlbInstance"} {
   263  			if strings.HasPrefix(associatedId, prefix) {
   264  				params["AssociatedInstanceType"] = instanceType
   265  			}
   266  		}
   267  	}
   268  
   269  	body, err := region.vpcRequest("DescribeEipAddresses", params)
   270  	if err != nil {
   271  		log.Errorf("DescribeEipAddresses fail %s", err)
   272  		return nil, 0, err
   273  	}
   274  
   275  	eips := make([]SEipAddress, 0)
   276  	err = body.Unmarshal(&eips, "EipAddresses", "EipAddress")
   277  	if err != nil {
   278  		log.Errorf("Unmarshal EipAddress details fail %s", err)
   279  		return nil, 0, err
   280  	}
   281  	total, _ := body.Int("TotalCount")
   282  	for i := 0; i < len(eips); i += 1 {
   283  		eips[i].region = region
   284  	}
   285  	return eips, int(total), nil
   286  }
   287  
   288  func (region *SRegion) GetEip(eipId string) (*SEipAddress, error) {
   289  	eips, total, err := region.GetEips(eipId, "", "", 0, 1)
   290  	if err != nil {
   291  		return nil, err
   292  	}
   293  	if total != 1 {
   294  		return nil, cloudprovider.ErrNotFound
   295  	}
   296  	return &eips[0], nil
   297  }
   298  
   299  func (region *SRegion) AllocateEIP(name string, bwMbps int, chargeType TInternetChargeType, projectId string) (*SEipAddress, error) {
   300  	params := make(map[string]string)
   301  
   302  	if len(name) > 0 {
   303  		params["Name"] = name
   304  	}
   305  	params["Bandwidth"] = fmt.Sprintf("%d", bwMbps)
   306  	params["InternetChargeType"] = string(chargeType)
   307  	params["InstanceChargeType"] = "PostPaid"
   308  	params["ClientToken"] = utils.GenRequestId(20)
   309  	if len(projectId) > 0 {
   310  		params["ResourceGroupId"] = projectId
   311  	}
   312  
   313  	body, err := region.vpcRequest("AllocateEipAddress", params)
   314  	if err != nil {
   315  		log.Errorf("AllocateEipAddress fail %s", err)
   316  		return nil, err
   317  	}
   318  
   319  	eipId, err := body.GetString("AllocationId")
   320  	if err != nil {
   321  		log.Errorf("fail to get AllocationId after EIP allocation??? %s", err)
   322  		return nil, err
   323  	}
   324  
   325  	return region.GetEip(eipId)
   326  }
   327  
   328  func (region *SRegion) CreateEIP(opts *cloudprovider.SEip) (cloudprovider.ICloudEIP, error) {
   329  	var ctype TInternetChargeType
   330  	switch opts.ChargeType {
   331  	case api.EIP_CHARGE_TYPE_BY_TRAFFIC:
   332  		ctype = InternetChargeByTraffic
   333  	case api.EIP_CHARGE_TYPE_BY_BANDWIDTH:
   334  		ctype = InternetChargeByBandwidth
   335  	}
   336  	return region.AllocateEIP(opts.Name, opts.BandwidthMbps, ctype, opts.ProjectId)
   337  }
   338  
   339  func (region *SRegion) DeallocateEIP(eipId string) error {
   340  	params := make(map[string]string)
   341  	params["AllocationId"] = eipId
   342  
   343  	_, err := region.vpcRequest("ReleaseEipAddress", params)
   344  	if err != nil {
   345  		log.Errorf("ReleaseEipAddress fail %s", err)
   346  	}
   347  	return err
   348  }
   349  
   350  func (region *SRegion) AssociateEip(eipId string, instanceId string) error {
   351  	params := make(map[string]string)
   352  	params["AllocationId"] = eipId
   353  	params["InstanceId"] = instanceId
   354  	for prefix, instanceType := range map[string]string{"i-": "EcsInstance", "lb-": "SlbInstance", "ngw-": "Nat"} {
   355  		if strings.HasPrefix(instanceId, prefix) {
   356  			params["InstanceType"] = instanceType
   357  		}
   358  	}
   359  
   360  	_, err := region.vpcRequest("AssociateEipAddress", params)
   361  	return errors.Wrapf(err, "AssociateEipAddress")
   362  }
   363  
   364  func (region *SRegion) DissociateEip(eipId string, instanceId string) error {
   365  	params := make(map[string]string)
   366  	params["AllocationId"] = eipId
   367  	params["InstanceId"] = instanceId
   368  	for prefix, instanceType := range map[string]string{"i-": "EcsInstance", "lb-": "SlbInstance", "ngw-": "Nat"} {
   369  		if strings.HasPrefix(instanceId, prefix) {
   370  			params["InstanceType"] = instanceType
   371  		}
   372  	}
   373  
   374  	_, err := region.vpcRequest("UnassociateEipAddress", params)
   375  	if err != nil {
   376  		log.Errorf("UnassociateEipAddress fail %s", err)
   377  	}
   378  	return err
   379  }
   380  
   381  func (region *SRegion) UpdateEipBandwidth(eipId string, bw int) error {
   382  	params := make(map[string]string)
   383  	params["AllocationId"] = eipId
   384  	params["Bandwidth"] = fmt.Sprintf("%d", bw)
   385  
   386  	_, err := region.vpcRequest("ModifyEipAddressAttribute", params)
   387  	if err != nil {
   388  		log.Errorf("ModifyEipAddressAttribute fail %s", err)
   389  	}
   390  	return err
   391  }
   392  
   393  func (self *SEipAddress) GetProjectId() string {
   394  	return self.ResourceGroupId
   395  }