// Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package aliyun import ( "fmt" "strings" "time" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" api "yunion.io/x/cloudmux/pkg/apis/compute" "yunion.io/x/cloudmux/pkg/cloudprovider" "yunion.io/x/cloudmux/pkg/multicloud" ) type TInternetChargeType string const ( InternetChargeByTraffic = TInternetChargeType("PayByTraffic") InternetChargeByBandwidth = TInternetChargeType("PayByBandwidth") ) const ( EIP_STATUS_ASSOCIATING = "Associating" EIP_STATUS_UNASSOCIATING = "Unassociating" EIP_STATUS_INUSE = "InUse" EIP_STATUS_AVAILABLE = "Available" EIP_OPERATION_LOCK_FINANCIAL = "financial" EIP_OPERATION_LOCK_SECURITY = "security" EIP_INSTANCE_TYPE_ECS = "EcsInstance" // (默认值):VPC类型的ECS实例 EIP_INTANNCE_TYPE_SLB = "SlbInstance" // :VPC类型的SLB实例 EIP_INSTANCE_TYPE_NAT = "Nat" // :NAT网关 EIP_INSTANCE_TYPE_HAVIP = "HaVip" // :HAVIP ) /* { "AllocationId":"eip-2zeddtan63ou44dtyt9s3", "AllocationTime":"2019-02-23T06:48:36Z", "Bandwidth":"100", "ChargeType":"PostPaid", "ExpiredTime":"", "InstanceId":"", "InstanceType":"", "InternetChargeType":"PayByTraffic", "IpAddress":"39.105.131.32", "OperationLocks":{"LockReason":[]}, "RegionId":"cn-beijing", "Status":"Available" } */ type SEipAddress struct { region *SRegion multicloud.SEipBase AliyunTags Name string AllocationId string InternetChargeType TInternetChargeType IpAddress string Status string InstanceType string InstanceId string Bandwidth int /* Mbps */ BusinessStatus string AllocationTime time.Time DeletionProtection bool Descritpion string ISP string Mode string Netmode string OperationLocks string ChargeType TChargeType ExpiredTime time.Time ResourceGroupId string } func (self *SEipAddress) GetId() string { return self.AllocationId } func (self *SEipAddress) GetName() string { if len(self.Name) > 0 { return self.Name } return self.IpAddress } func (self *SEipAddress) GetGlobalId() string { return self.AllocationId } func (self *SEipAddress) GetStatus() string { switch self.Status { case EIP_STATUS_AVAILABLE, EIP_STATUS_INUSE: return api.EIP_STATUS_READY case EIP_STATUS_ASSOCIATING: return api.EIP_STATUS_ASSOCIATE case EIP_STATUS_UNASSOCIATING: return api.EIP_STATUS_DISSOCIATE default: return api.EIP_STATUS_UNKNOWN } } func (self *SEipAddress) Refresh() error { if self.IsEmulated() { return nil } new, err := self.region.GetEip(self.AllocationId) if err != nil { return err } return jsonutils.Update(self, new) } func (self *SEipAddress) IsEmulated() bool { if self.AllocationId == self.InstanceId { // fixed Public IP return true } else { return false } } func (self *SEipAddress) GetIpAddr() string { return self.IpAddress } func (self *SEipAddress) GetMode() string { if self.InstanceId == self.AllocationId { return api.EIP_MODE_INSTANCE_PUBLICIP } else { return api.EIP_MODE_STANDALONE_EIP } } func (self *SEipAddress) GetAssociationType() string { switch self.InstanceType { case EIP_INSTANCE_TYPE_ECS, "NetworkInterface": if strings.HasPrefix(self.Name, "CREATE_BY_ALB") || strings.HasPrefix(self.Name, "CREATE_BY_NLB") { return api.EIP_ASSOCIATE_TYPE_LOADBALANCER } return api.EIP_ASSOCIATE_TYPE_SERVER case EIP_INSTANCE_TYPE_NAT: return api.EIP_ASSOCIATE_TYPE_NAT_GATEWAY case EIP_INTANNCE_TYPE_SLB: return api.EIP_ASSOCIATE_TYPE_LOADBALANCER default: return self.InstanceType } } func (self *SEipAddress) GetAssociationExternalId() string { return self.InstanceId } func (self *SEipAddress) GetBillingType() string { return convertChargeType(self.ChargeType) } func (self *SEipAddress) GetCreatedAt() time.Time { return self.AllocationTime } func (self *SEipAddress) GetExpiredAt() time.Time { return convertExpiredAt(self.ExpiredTime) } func (self *SEipAddress) Delete() error { return self.region.DeallocateEIP(self.AllocationId) } func (self *SEipAddress) GetBandwidth() int { return self.Bandwidth } func (self *SEipAddress) GetINetworkId() string { return "" } func (self *SEipAddress) GetInternetChargeType() string { switch self.InternetChargeType { case InternetChargeByTraffic: return api.EIP_CHARGE_TYPE_BY_TRAFFIC case InternetChargeByBandwidth: return api.EIP_CHARGE_TYPE_BY_BANDWIDTH default: return api.EIP_CHARGE_TYPE_BY_TRAFFIC } } func (self *SEipAddress) Associate(conf *cloudprovider.AssociateConfig) error { err := cloudprovider.Wait(20*time.Second, 60*time.Second, func() (bool, error) { err := self.region.AssociateEip(self.AllocationId, conf.InstanceId) if err != nil { if isError(err, "IncorrectInstanceStatus") { return false, nil } return false, errors.Wrap(err, "region.AssociateEip") } return true, nil }) err = cloudprovider.WaitStatus(self, api.EIP_STATUS_READY, 10*time.Second, 180*time.Second) return err } func (self *SEipAddress) Dissociate() error { err := self.region.DissociateEip(self.AllocationId, self.InstanceId) if err != nil { return err } err = cloudprovider.WaitStatus(self, api.EIP_STATUS_READY, 10*time.Second, 180*time.Second) return err } func (self *SEipAddress) ChangeBandwidth(bw int) error { return self.region.UpdateEipBandwidth(self.AllocationId, bw) } func (region *SRegion) GetEips(eipId string, associatedId, addr string) ([]SEipAddress, error) { params := make(map[string]string) params["RegionId"] = region.RegionId params["PageSize"] = "100" if len(addr) > 0 { params["EipAddress"] = addr } if len(eipId) > 0 { params["AllocationId"] = eipId } if len(associatedId) > 0 { params["AssociatedInstanceId"] = associatedId for prefix, instanceType := range map[string]string{"i-": "EcsInstance", "ngw-": "Nat", "lb-": "SlbInstance"} { if strings.HasPrefix(associatedId, prefix) { params["AssociatedInstanceType"] = instanceType } } } pageNumber := 1 ret := []SEipAddress{} for { params["PageNumber"] = fmt.Sprintf("%d", pageNumber) body, err := region.vpcRequest("DescribeEipAddresses", params) if err != nil { log.Errorf("DescribeEipAddresses fail %s", err) return nil, err } part := struct { EipAddresses struct { EipAddress []SEipAddress } `json:"EipAddresses"` TotalCount int `json:"TotalCount"` }{} err = body.Unmarshal(&part) if err != nil { return nil, errors.Wrapf(err, "Unmarshal EipAddress details") } ret = append(ret, part.EipAddresses.EipAddress...) if len(ret) >= part.TotalCount { break } pageNumber++ } return ret, nil } func (region *SRegion) GetEip(eipId string) (*SEipAddress, error) { eips, err := region.GetEips(eipId, "", "") if err != nil { return nil, err } for i := range eips { if eips[i].AllocationId == eipId { eips[i].region = region return &eips[i], nil } } return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s", eipId) } func (region *SRegion) AllocateEIP(opts *cloudprovider.SEip) (*SEipAddress, error) { params := make(map[string]string) if len(opts.Name) > 0 { params["Name"] = opts.Name } params["RegionId"] = region.RegionId params["Bandwidth"] = fmt.Sprintf("%d", opts.BandwidthMbps) switch opts.ChargeType { case api.EIP_CHARGE_TYPE_BY_TRAFFIC: params["InternetChargeType"] = string(InternetChargeByTraffic) case api.EIP_CHARGE_TYPE_BY_BANDWIDTH: params["InternetChargeType"] = string(InternetChargeByBandwidth) } params["InstanceChargeType"] = "PostPaid" params["ClientToken"] = utils.GenRequestId(20) if len(opts.ProjectId) > 0 { params["ResourceGroupId"] = opts.ProjectId } params["ISP"] = "BGP" if opts.BGPType == "BGP_PRO" { params["ISP"] = "BGP_PRO" } body, err := region.vpcRequest("AllocateEipAddress", params) if err != nil { return nil, errors.Wrapf(err, "AllocateEipAddress") } eipId, err := body.GetString("AllocationId") if err != nil { return nil, errors.Wrapf(err, "get AllocationId after created") } eip, err := region.GetEip(eipId) if err != nil { return nil, err } if len(opts.Tags) == 0 { return eip, nil } cloudprovider.WaitStatus(eip, api.EIP_STATUS_READY, time.Second*5, time.Minute*1) eip.SetTags(opts.Tags, false) return eip, nil } func (region *SRegion) CreateEIP(opts *cloudprovider.SEip) (cloudprovider.ICloudEIP, error) { return region.AllocateEIP(opts) } func (region *SRegion) DeallocateEIP(eipId string) error { params := make(map[string]string) params["AllocationId"] = eipId _, err := region.vpcRequest("ReleaseEipAddress", params) if err != nil { log.Errorf("ReleaseEipAddress fail %s", err) } return err } func (region *SRegion) AssociateEip(eipId string, instanceId string) error { params := make(map[string]string) params["AllocationId"] = eipId params["InstanceId"] = instanceId for prefix, instanceType := range map[string]string{"i-": "EcsInstance", "lb-": "SlbInstance", "ngw-": "Nat"} { if strings.HasPrefix(instanceId, prefix) { params["InstanceType"] = instanceType } } _, err := region.vpcRequest("AssociateEipAddress", params) return errors.Wrapf(err, "AssociateEipAddress") } func (region *SRegion) DissociateEip(eipId string, instanceId string) error { params := make(map[string]string) params["AllocationId"] = eipId params["InstanceId"] = instanceId for prefix, instanceType := range map[string]string{"i-": "EcsInstance", "lb-": "SlbInstance", "ngw-": "Nat"} { if strings.HasPrefix(instanceId, prefix) { params["InstanceType"] = instanceType } } _, err := region.vpcRequest("UnassociateEipAddress", params) if err != nil { log.Errorf("UnassociateEipAddress fail %s", err) } return err } func (region *SRegion) UpdateEipBandwidth(eipId string, bw int) error { params := make(map[string]string) params["AllocationId"] = eipId params["Bandwidth"] = fmt.Sprintf("%d", bw) _, err := region.vpcRequest("ModifyEipAddressAttribute", params) if err != nil { log.Errorf("ModifyEipAddressAttribute fail %s", err) } return err } func (self *SEipAddress) GetProjectId() string { return self.ResourceGroupId } func (self *SEipAddress) SetTags(tags map[string]string, replace bool) error { return self.region.SetResourceTags(ALIYUN_SERVICE_VPC, "EIP", self.AllocationId, tags, replace) }