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 }