yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/qcloud/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 qcloud 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 26 billing_api "yunion.io/x/cloudmux/pkg/apis/billing" 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 const ( 33 EIP_STATUS_CREATING = "CREATING" 34 EIP_STATUS_BINDING = "BINDING" 35 EIP_STATUS_BIND = "BIND" 36 EIP_STATUS_UNBINDING = "UNBINDING" 37 EIP_STATUS_UNBIND = "UNBIND" 38 EIP_STATUS_OFFLINING = "OFFLINING" 39 EIP_STATUS_BIND_ENI = "BIND_ENI" 40 EIP_STATUS_CREATE_FAILED = "CREATE_FAILED" 41 42 EIP_TYPE_CALCIP = "CalcIP" //表示设备ip 43 EIP_TYPE_WANIP = "WanIP" //普通公网ip 44 EIP_TYPE_EIP = "EIP" //弹性公网ip 45 EIP_TYPE_ANYCASTEIP = "AnycastEIP" //加速EIP 46 ) 47 48 type SEipAddress struct { 49 region *SRegion 50 multicloud.SEipBase 51 QcloudTags 52 53 AddressId string // EIP的ID,是EIP的唯一标识。 54 AddressName string // EIP名称。 55 AddressStatus string // EIP状态。 56 AddressIp string // 外网IP地址 57 InstanceId string // 绑定的资源实例ID。可能是一个CVM,NAT。 58 CreatedTime time.Time // 创建时间。按照ISO8601标准表示,并且使用UTC时间。格式为:YYYY-MM-DDThh:mm:ssZ。 59 NetworkInterfaceId string // 绑定的弹性网卡ID 60 PrivateAddressIp string // 绑定的资源内网ip 61 IsArrears bool // 资源隔离状态。true表示eip处于隔离状态,false表示资源处于未隔离装填 62 IsBlocked bool // 资源封堵状态。true表示eip处于封堵状态,false表示eip处于未封堵状态 63 IsEipDirectConnection bool // eip是否支持直通模式。true表示eip支持直通模式,false表示资源不支持直通模式 64 AddressType string // eip资源类型,包括"CalcIP","WanIP","EIP","AnycastEIP"。其中"CalcIP"表示设备ip,“WanIP”表示普通公网ip,“EIP”表示弹性公网ip,“AnycastEip”表示加速EIP 65 CascadeRelease bool // eip是否在解绑后自动释放。true表示eip将会在解绑后自动释放,false表示eip在解绑后不会自动释放 66 Bandwidth int 67 InternetChargeType string 68 } 69 70 func (self *SEipAddress) GetId() string { 71 return self.AddressId 72 } 73 74 func (self *SEipAddress) GetName() string { 75 if len(self.AddressName) > 0 && self.AddressName != "未命名" { 76 return self.AddressName 77 } 78 return self.AddressId 79 } 80 81 func (self *SEipAddress) GetGlobalId() string { 82 return self.AddressId 83 } 84 85 func (self *SEipAddress) GetStatus() string { 86 switch self.AddressStatus { 87 case EIP_STATUS_CREATING: 88 return api.EIP_STATUS_ALLOCATE 89 case EIP_STATUS_BINDING: 90 return api.EIP_STATUS_ASSOCIATE 91 case EIP_STATUS_UNBINDING: 92 return api.EIP_STATUS_DISSOCIATE 93 case EIP_STATUS_UNBIND, EIP_STATUS_BIND, EIP_STATUS_OFFLINING, EIP_STATUS_BIND_ENI: 94 return api.EIP_STATUS_READY 95 case EIP_STATUS_CREATE_FAILED: 96 return api.EIP_STATUS_ALLOCATE_FAIL 97 default: 98 return api.EIP_STATUS_UNKNOWN 99 } 100 } 101 102 func (self *SEipAddress) Refresh() error { 103 if self.IsEmulated() { 104 return nil 105 } 106 new, err := self.region.GetEip(self.AddressId) 107 if err != nil { 108 return err 109 } 110 return jsonutils.Update(self, new) 111 } 112 113 func (self *SEipAddress) IsEmulated() bool { 114 if self.AddressId == self.InstanceId { 115 // fixed Public IP 116 return true 117 } else { 118 return false 119 } 120 } 121 122 func (self *SEipAddress) GetIpAddr() string { 123 return self.AddressIp 124 } 125 126 func (self *SEipAddress) GetMode() string { 127 if self.InstanceId == self.AddressId { 128 return api.EIP_MODE_INSTANCE_PUBLICIP 129 } 130 return api.EIP_MODE_STANDALONE_EIP 131 } 132 133 func (self *SEipAddress) GetAssociationType() string { 134 if len(self.InstanceId) > 0 { 135 for prefix, instanceType := range map[string]string{ 136 "nat-": api.EIP_ASSOCIATE_TYPE_NAT_GATEWAY, 137 "ins-": api.EIP_ASSOCIATE_TYPE_SERVER, 138 "lb-": api.EIP_ASSOCIATE_TYPE_LOADBALANCER, 139 "lbl-": api.EIP_ASSOCIATE_TYPE_LOADBALANCER, 140 } { 141 if strings.HasPrefix(self.InstanceId, prefix) { 142 return instanceType 143 } 144 } 145 if info := strings.Split(self.InstanceId, "-"); len(info) > 0 { 146 return info[0] 147 } 148 return self.InstanceId 149 } 150 return "" 151 } 152 153 func (self *SEipAddress) GetAssociationExternalId() string { 154 return self.InstanceId 155 } 156 157 func (self *SEipAddress) Delete() error { 158 return self.region.DeallocateEIP(self.AddressId) 159 } 160 161 func (self *SEipAddress) GetBandwidth() int { 162 if self.Bandwidth > 0 { 163 return self.Bandwidth 164 } 165 if len(self.InstanceId) > 0 { 166 if strings.HasPrefix(self.InstanceId, "ins-") { 167 if instance, err := self.region.GetInstance(self.InstanceId); err == nil { 168 return instance.InternetAccessible.InternetMaxBandwidthOut 169 } 170 } 171 } 172 return 0 173 } 174 175 func (self *SEipAddress) GetINetworkId() string { 176 return "" 177 } 178 179 func (self *SEipAddress) GetBillingType() string { 180 return billing_api.BILLING_TYPE_POSTPAID 181 } 182 183 func (self *SEipAddress) GetCreatedAt() time.Time { 184 return self.CreatedTime 185 } 186 187 func (self *SEipAddress) GetExpiredAt() time.Time { 188 return time.Time{} 189 } 190 191 func (self *SEipAddress) GetInternetChargeType() string { 192 switch self.InternetChargeType { 193 case "TRAFFIC_POSTPAID_BY_HOUR": 194 return api.EIP_CHARGE_TYPE_BY_TRAFFIC 195 case "BANDWIDTH_PACKAGE", "BANDWIDTH_POSTPAID_BY_HOUR", "BANDWIDTH_PREPAID_BY_MONTH": 196 return api.EIP_CHARGE_TYPE_BY_BANDWIDTH 197 } 198 if len(self.InstanceId) > 0 { 199 if strings.HasPrefix(self.InstanceId, "ins-") { 200 if instance, err := self.region.GetInstance(self.InstanceId); err == nil { 201 switch instance.InternetAccessible.InternetChargeType { 202 case InternetChargeTypeTrafficPostpaidByHour: 203 return api.EIP_CHARGE_TYPE_BY_TRAFFIC 204 default: 205 return api.EIP_CHARGE_TYPE_BY_BANDWIDTH 206 } 207 } 208 } 209 } 210 return "" 211 } 212 213 func (self *SEipAddress) Associate(conf *cloudprovider.AssociateConfig) error { 214 err := self.region.AssociateEip(self.AddressId, conf.InstanceId) 215 if err != nil { 216 return err 217 } 218 if conf.Bandwidth > 0 && self.Bandwidth == 0 { 219 err = self.region.UpdateInstanceBandwidth(conf.InstanceId, conf.Bandwidth, conf.ChargeType) 220 if err != nil { 221 log.Warningf("failed to change instance %s bandwidth -> %d error: %v", conf.InstanceId, conf.Bandwidth, err) 222 } 223 } 224 return cloudprovider.WaitStatusWithDelay(self, api.EIP_STATUS_READY, 5*time.Second, 10*time.Second, 180*time.Second) 225 } 226 227 func (self *SEipAddress) Dissociate() error { 228 err := self.region.DissociateEip(self.AddressId) 229 if err != nil { 230 return err 231 } 232 return cloudprovider.WaitStatusWithDelay(self, api.EIP_STATUS_READY, 5*time.Second, 10*time.Second, 180*time.Second) 233 } 234 235 func (self *SEipAddress) ChangeBandwidth(bw int) error { 236 if len(self.InstanceId) > 0 && self.Bandwidth == 0 && self.GetAssociationType() == api.EIP_ASSOCIATE_TYPE_SERVER { 237 return self.region.UpdateInstanceBandwidth(self.InstanceId, bw, "") 238 } 239 if len(self.InternetChargeType) > 0 { 240 return self.region.ChangeEipBindWidth(self.AddressId, bw, self.InternetChargeType) 241 } 242 return nil 243 } 244 245 func (region *SRegion) GetEips(eipId string, instanceId string, offset int, limit int) ([]SEipAddress, int, error) { 246 if limit > 50 || limit <= 0 { 247 limit = 50 248 } 249 250 params := make(map[string]string) 251 params["Limit"] = fmt.Sprintf("%d", limit) 252 params["Offset"] = fmt.Sprintf("%d", offset) 253 254 if len(eipId) > 0 { 255 params["AddressIds.0"] = eipId 256 } 257 258 if len(instanceId) > 0 { 259 params["Filters.0.Name"] = "instance-id" 260 params["Filters.0.Values.0"] = instanceId 261 } 262 263 body, err := region.vpcRequest("DescribeAddresses", params) 264 if err != nil { 265 log.Errorf("DescribeEipAddresses fail %s", err) 266 return nil, 0, err 267 } 268 269 eips := make([]SEipAddress, 0) 270 err = body.Unmarshal(&eips, "AddressSet") 271 if err != nil { 272 log.Errorf("Unmarshal EipAddress details fail %s", err) 273 return nil, 0, err 274 } 275 total, _ := body.Float("TotalCount") 276 for i := 0; i < len(eips); i++ { 277 eips[i].region = region 278 } 279 return eips, int(total), nil 280 } 281 282 func (region *SRegion) GetEip(eipId string) (*SEipAddress, error) { 283 eips, total, err := region.GetEips(eipId, "", 0, 1) 284 if err != nil { 285 return nil, err 286 } 287 if total != 1 { 288 return nil, cloudprovider.ErrNotFound 289 } 290 return &eips[0], nil 291 } 292 293 func (region *SRegion) AllocateEIP(name string, bwMbps int, chargeType string) (*SEipAddress, error) { 294 params := make(map[string]string) 295 params["AddressName"] = name 296 if len(name) > 20 { 297 params["AddressName"] = name[:20] 298 } 299 if bwMbps > 0 { 300 params["InternetMaxBandwidthOut"] = fmt.Sprintf("%d", bwMbps) 301 } 302 303 _, totalCount, err := region.GetBandwidthPackages([]string{}, 0, 50) 304 if err != nil { 305 return nil, errors.Wrapf(err, "GetBandwidthPackages") 306 } 307 if totalCount == 0 { 308 switch chargeType { 309 case api.EIP_CHARGE_TYPE_BY_TRAFFIC: 310 params["InternetChargeType"] = "TRAFFIC_POSTPAID_BY_HOUR" 311 case api.EIP_CHARGE_TYPE_BY_BANDWIDTH: 312 params["InternetChargeType"] = "BANDWIDTH_POSTPAID_BY_HOUR" 313 } 314 } 315 316 addRessSet := []string{} 317 body, err := region.vpcRequest("AllocateAddresses", params) 318 if err != nil { 319 return nil, errors.Wrapf(err, "AllocateAddresses") 320 } 321 err = body.Unmarshal(&addRessSet, "AddressSet") 322 if err != nil { 323 return nil, errors.Wrapf(err, "resp.Unmarshal") 324 } 325 return region.GetEip(addRessSet[0]) 326 } 327 328 // https://cloud.tencent.com/document/api/215/16699 329 // 腾讯云eip不支持指定项目 330 func (region *SRegion) CreateEIP(eip *cloudprovider.SEip) (cloudprovider.ICloudEIP, error) { 331 return region.AllocateEIP(eip.Name, eip.BandwidthMbps, eip.ChargeType) 332 } 333 334 func (region *SRegion) DeallocateEIP(eipId string) error { 335 params := make(map[string]string) 336 params["Region"] = region.Region 337 params["AddressIds.0"] = eipId 338 339 _, err := region.vpcRequest("ReleaseAddresses", params) 340 return errors.Wrapf(err, "ReleaseAddresses") 341 } 342 343 func (region *SRegion) AssociateEip(eipId string, instanceId string) error { 344 params := make(map[string]string) 345 params["AddressId"] = eipId 346 params["InstanceId"] = instanceId 347 348 _, err := region.vpcRequest("AssociateAddress", params) 349 return errors.Wrapf(err, "AssociateAddress") 350 } 351 352 func (region *SRegion) DissociateEip(eipId string) error { 353 params := make(map[string]string) 354 params["Region"] = region.Region 355 params["AddressId"] = eipId 356 357 _, err := region.vpcRequest("DisassociateAddress", params) 358 return errors.Wrapf(err, "DisassociateAddress") 359 } 360 361 func (self *SRegion) UpdateInstanceBandwidth(instanceId string, bw int, chargeType string) error { 362 params := make(map[string]string) 363 params["Region"] = self.Region 364 params["InternetAccessible.InternetMaxBandwidthOut"] = fmt.Sprintf("%d", bw) 365 366 _, totalCount, err := self.GetBandwidthPackages([]string{}, 0, 50) 367 if err != nil { 368 return errors.Wrapf(err, "GetBandwidthPackages") 369 } 370 if totalCount > 0 { 371 params["InstanceIds.0"] = instanceId 372 _, err = self.cvmRequest("ResetInstancesInternetMaxBandwidth", params, true) 373 return errors.Wrapf(err, "ResetInstancesInternetMaxBandwidth") 374 } 375 internetChargeType := "TRAFFIC_POSTPAID_BY_HOUR" 376 if chargeType == api.EIP_CHARGE_TYPE_BY_BANDWIDTH { 377 internetChargeType = "BANDWIDTH_POSTPAID_BY_HOUR" 378 } 379 params["InternetAccessible.InternetChargeType"] = internetChargeType 380 action := "ResetInstancesInternetMaxBandwidth" 381 382 instance, err := self.GetInstance(instanceId) 383 if err != nil { 384 return errors.Wrapf(err, "GetInstance(%s)", instanceId) 385 } 386 if instance.InternetAccessible.InternetChargeType != internetChargeType { //避免 Code=InvalidParameterValue, Message=参数`InternetChargeType`中`TRAFFIC_POSTPAID_BY_HOUR`没有更改, RequestId=6be2f9bc-a967-41db-9f0d-aff789c703ca 387 params["InstanceId"] = instanceId 388 action = "ModifyInstanceInternetChargeType" 389 } else { 390 params["InstanceIds.0"] = instanceId 391 } 392 err = cloudprovider.Wait(time.Second*5, time.Minute*3, func() (bool, error) { 393 instance, err := self.GetInstance(instanceId) 394 if err != nil { 395 return false, errors.Wrapf(err, "GetInstance(%s)", instanceId) 396 } 397 _bw := instance.InternetAccessible.InternetMaxBandwidthOut 398 log.Infof("%s bandwidth from %d -> %d expect %d", action, _bw, bw, bw) 399 if _bw == bw { 400 return true, nil 401 } 402 if _, err := self.cvmRequest(action, params, true); err != nil { 403 log.Errorf("%s %v", action, err) 404 return false, nil 405 } 406 return false, nil 407 }) 408 return errors.Wrapf(err, "cloudprovider.Wait bandwidth changed") 409 } 410 411 func (self *SRegion) ChangeEipBindWidth(eipId string, bw int, chargeType string) error { 412 params := map[string]string{ 413 "Region": self.Region, 414 "InternetMaxBandwidthOut": fmt.Sprintf("%d", bw), 415 "InternetChargeType": chargeType, 416 "AddressId": eipId, 417 } 418 _, err := self.vpcRequest("ModifyAddressInternetChargeType", params) 419 return errors.Wrapf(err, "ModifyAddressInternetChargeType") 420 } 421 422 func (self *SEipAddress) GetProjectId() string { 423 return "" 424 }