yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aws/loadbalancerbackendgroup.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 aws 16 17 import ( 18 "context" 19 "fmt" 20 "strconv" 21 "strings" 22 23 "github.com/aws/aws-sdk-go/service/elbv2" 24 "github.com/pkg/errors" 25 26 "yunion.io/x/jsonutils" 27 "yunion.io/x/log" 28 "yunion.io/x/pkg/utils" 29 30 api "yunion.io/x/cloudmux/pkg/apis/compute" 31 "yunion.io/x/cloudmux/pkg/cloudprovider" 32 "yunion.io/x/cloudmux/pkg/multicloud" 33 ) 34 35 type SElbBackendGroup struct { 36 multicloud.SResourceBase 37 AwsTags 38 region *SRegion 39 lb *SElb 40 41 TargetGroupName string `json:"TargetGroupName"` 42 Protocol string `json:"Protocol"` 43 Port int64 `json:"Port"` 44 VpcID string `json:"VpcId"` 45 TargetType string `json:"TargetType"` 46 HealthyThresholdCount int `json:"HealthyThresholdCount"` 47 Matcher Matcher `json:"Matcher"` 48 UnhealthyThresholdCount int `json:"UnhealthyThresholdCount"` 49 HealthCheckPath string `json:"HealthCheckPath"` 50 HealthCheckProtocol string `json:"HealthCheckProtocol"` 51 HealthCheckPort string `json:"HealthCheckPort"` 52 HealthCheckIntervalSeconds int `json:"HealthCheckIntervalSeconds"` 53 HealthCheckTimeoutSeconds int `json:"HealthCheckTimeoutSeconds"` 54 TargetGroupArn string `json:"TargetGroupArn"` 55 LoadBalancerArns []string `json:"LoadBalancerArns"` 56 } 57 58 func (self *SElbBackendGroup) GetLoadbalancerId() string { 59 if len(self.LoadBalancerArns) > 0 { 60 return self.LoadBalancerArns[0] 61 } 62 63 return "" 64 } 65 66 func (self *SElbBackendGroup) GetILoadbalancer() cloudprovider.ICloudLoadbalancer { 67 return self.lb 68 } 69 70 type Matcher struct { 71 HTTPCode string `json:"HttpCode"` 72 } 73 74 func (self *SElbBackendGroup) GetId() string { 75 return self.TargetGroupArn 76 } 77 78 func (self *SElbBackendGroup) GetName() string { 79 return self.TargetGroupName 80 } 81 82 func (self *SElbBackendGroup) GetGlobalId() string { 83 return self.GetId() 84 } 85 86 func (self *SElbBackendGroup) GetStatus() string { 87 return api.LB_STATUS_ENABLED 88 } 89 90 func (self *SElbBackendGroup) Refresh() error { 91 lbbg, err := self.region.GetElbBackendgroup(self.GetId()) 92 if err != nil { 93 return err 94 } 95 96 err = jsonutils.Update(self, lbbg) 97 if err != nil { 98 return err 99 } 100 101 return nil 102 } 103 104 func (self *SElbBackendGroup) IsEmulated() bool { 105 return false 106 } 107 108 func (self *SElbBackendGroup) GetSysTags() map[string]string { 109 data := map[string]string{} 110 data["port"] = strconv.FormatInt(self.Port, 10) 111 data["target_type"] = self.TargetType 112 data["health_check_protocol"] = strings.ToLower(self.HealthCheckProtocol) 113 data["health_check_interval"] = strconv.Itoa(self.HealthCheckIntervalSeconds) 114 return data 115 } 116 117 func (self *SElbBackendGroup) GetProjectId() string { 118 return "" 119 } 120 121 func (self *SElbBackendGroup) IsDefault() bool { 122 return false 123 } 124 125 func (self *SElbBackendGroup) GetType() string { 126 return api.LB_BACKENDGROUP_TYPE_NORMAL 127 } 128 129 func (self *SElbBackendGroup) GetILoadbalancerBackends() ([]cloudprovider.ICloudLoadbalancerBackend, error) { 130 backends, err := self.region.GetELbBackends(self.GetId()) 131 if err != nil { 132 return nil, errors.Wrap(err, "GetELbBackends") 133 } 134 135 ibackends := make([]cloudprovider.ICloudLoadbalancerBackend, len(backends)) 136 for i := range backends { 137 backends[i].region = self.region 138 backends[i].group = self 139 ibackends[i] = &backends[i] 140 } 141 142 return ibackends, nil 143 } 144 145 func (self *SElbBackendGroup) GetILoadbalancerBackendById(backendId string) (cloudprovider.ICloudLoadbalancerBackend, error) { 146 backend, err := self.region.GetELbBackend(backendId) 147 if err != nil { 148 return nil, errors.Wrap(err, "GetELbBackend") 149 } 150 151 backend.group = self 152 return backend, nil 153 } 154 155 func (self *SElbBackendGroup) GetProtocolType() string { 156 switch self.Protocol { 157 case "TCP": 158 return api.LB_LISTENER_TYPE_TCP 159 case "UDP": 160 return api.LB_LISTENER_TYPE_UDP 161 case "HTTP": 162 return api.LB_LISTENER_TYPE_HTTP 163 case "HTTPS": 164 return api.LB_LISTENER_TYPE_HTTPS 165 case "TCP_UDP": 166 return api.LB_LISTENER_TYPE_TCP_UDP 167 default: 168 return "" 169 } 170 } 171 172 func (self *SElbBackendGroup) GetScheduler() string { 173 return "" 174 } 175 176 func (self *SElbBackendGroup) GetHealthCheck() (*cloudprovider.SLoadbalancerHealthCheck, error) { 177 health := &cloudprovider.SLoadbalancerHealthCheck{} 178 health.HealthCheck = api.LB_BOOL_ON 179 health.HealthCheckRise = self.HealthyThresholdCount 180 health.HealthCheckFail = self.UnhealthyThresholdCount 181 health.HealthCheckInterval = self.HealthCheckIntervalSeconds 182 health.HealthCheckURI = self.HealthCheckPath 183 health.HealthCheckType = strings.ToLower(self.HealthCheckProtocol) 184 health.HealthCheckTimeout = self.HealthCheckTimeoutSeconds 185 health.HealthCheckHttpCode = ToOnecloudHealthCode(self.Matcher.HTTPCode) 186 return health, nil 187 } 188 189 func (self *SElbBackendGroup) GetStickySession() (*cloudprovider.SLoadbalancerStickySession, error) { 190 attrs, err := self.region.GetElbBackendgroupAttributesById(self.GetId()) 191 if err != nil { 192 return nil, errors.Wrap(err, "GetElbBackendgroupAttributesById") 193 } 194 195 cookieTime := 0 196 if t, ok := attrs["stickiness.lb_cookie.duration_seconds"]; !ok { 197 cookieTime, err = strconv.Atoi(t) 198 } 199 200 ret := &cloudprovider.SLoadbalancerStickySession{ 201 StickySession: attrs["stickiness.enabled"], 202 StickySessionCookie: "", 203 StickySessionType: api.LB_STICKY_SESSION_TYPE_INSERT, 204 StickySessionCookieTimeout: cookieTime, 205 } 206 207 return ret, nil 208 } 209 210 func (self *SElbBackendGroup) AddBackendServer(serverId string, weight int, port int) (cloudprovider.ICloudLoadbalancerBackend, error) { 211 backend, err := self.region.AddElbBackend(self.GetId(), serverId, weight, port) 212 if err != nil { 213 return nil, errors.Wrap(err, "AddElbBackend") 214 } 215 216 backend.region = self.region 217 backend.group = self 218 return backend, nil 219 } 220 221 func (self *SElbBackendGroup) RemoveBackendServer(serverId string, weight int, port int) error { 222 return self.region.RemoveElbBackend(self.GetId(), serverId, weight, port) 223 } 224 225 func (self *SElbBackendGroup) Delete(ctx context.Context) error { 226 return self.region.DeleteElbBackendGroup(self.GetId()) 227 } 228 229 func (self *SElbBackendGroup) Sync(ctx context.Context, group *cloudprovider.SLoadbalancerBackendGroup) error { 230 return self.region.SyncELbBackendGroup(self.GetId(), group) 231 } 232 233 func (self *SRegion) GetELbBackends(backendgroupId string) ([]SElbBackend, error) { 234 client, err := self.GetElbV2Client() 235 if err != nil { 236 return nil, errors.Wrap(err, "GetElbV2Client") 237 } 238 239 group, err := self.GetElbBackendgroup(backendgroupId) 240 if err != nil { 241 return nil, errors.Wrap(err, "GetElbBackendgroup") 242 } 243 244 params := &elbv2.DescribeTargetHealthInput{} 245 params.SetTargetGroupArn(backendgroupId) 246 output, err := client.DescribeTargetHealth(params) 247 if err != nil { 248 return nil, errors.Wrap(err, "DescribeTargetHealth") 249 } 250 251 backends := []SElbBackend{} 252 err = unmarshalAwsOutput(output, "TargetHealthDescriptions", &backends) 253 if err != nil { 254 return nil, errors.Wrap(err, "unmarshalAwsOutput.TargetHealthDescriptions") 255 } 256 257 ret := []SElbBackend{} 258 for i := range backends { 259 if !utils.IsInStringArray(backends[i].TargetHealth.Reason, []string{"Target.InvalidState", "Target.NotInUse", "Target.DeregistrationInProgress"}) { 260 backends[i].region = self 261 backends[i].group = group 262 ret = append(ret, backends[i]) 263 } 264 } 265 266 return ret, nil 267 } 268 269 func (self *SRegion) GetELbBackend(backendId string) (*SElbBackend, error) { 270 client, err := self.GetElbV2Client() 271 if err != nil { 272 return nil, errors.Wrap(err, "GetElbV2Client") 273 } 274 275 groupId, instanceId, port, err := parseElbBackendId(backendId) 276 if err != nil { 277 log.Errorf("parseElbBackendId %s: %s", backendId, err) 278 return nil, errors.Wrap(err, "parseElbBackendId") 279 } 280 281 params := &elbv2.DescribeTargetHealthInput{} 282 desc := &elbv2.TargetDescription{} 283 desc.SetPort(int64(port)) 284 desc.SetId(instanceId) 285 params.SetTargets([]*elbv2.TargetDescription{desc}) 286 params.SetTargetGroupArn(groupId) 287 ret, err := client.DescribeTargetHealth(params) 288 if err != nil { 289 return nil, errors.Wrap(err, "DescribeTargetHealth") 290 } 291 292 backends := []SElbBackend{} 293 err = unmarshalAwsOutput(ret, "TargetHealthDescriptions", &backends) 294 if err != nil { 295 return nil, errors.Wrap(err, "unmarshalAwsOutput.TargetHealthDescriptions") 296 } 297 298 if len(backends) == 1 { 299 backends[0].region = self 300 return &backends[0], nil 301 } 302 303 return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetELbBackend") 304 } 305 306 func parseElbBackendId(id string) (string, string, int, error) { 307 segs := strings.Split(id, "::") 308 if len(segs) != 3 { 309 return "", "", 0, fmt.Errorf("%s is not a valid backend id", id) 310 } 311 312 port, err := strconv.Atoi(segs[2]) 313 if err != nil { 314 return "", "", 0, fmt.Errorf("%s is not a valid backend id, %s", id, err) 315 } 316 317 return segs[0], segs[1], port, nil 318 } 319 320 func genElbBackendId(backendgroupId string, serverId string, port int) string { 321 return strings.Join([]string{backendgroupId, serverId, strconv.Itoa(port)}, "::") 322 } 323 324 func (self *SRegion) AddElbBackend(backendgroupId, serverId string, weight int, port int) (*SElbBackend, error) { 325 client, err := self.GetElbV2Client() 326 if err != nil { 327 return nil, errors.Wrap(err, "GetElbV2Client") 328 } 329 330 params := &elbv2.RegisterTargetsInput{} 331 params.SetTargetGroupArn(backendgroupId) 332 desc := &elbv2.TargetDescription{} 333 desc.SetId(serverId) 334 desc.SetPort(int64(port)) 335 params.SetTargets([]*elbv2.TargetDescription{desc}) 336 _, err = client.RegisterTargets(params) 337 if err != nil { 338 return nil, errors.Wrap(err, "RegisterTargets") 339 } 340 341 return self.GetELbBackend(genElbBackendId(backendgroupId, serverId, port)) 342 } 343 344 func (self *SRegion) RemoveElbBackend(backendgroupId, serverId string, weight int, port int) error { 345 client, err := self.GetElbV2Client() 346 if err != nil { 347 return errors.Wrap(err, "GetElbV2Client") 348 } 349 350 params := &elbv2.DeregisterTargetsInput{} 351 params.SetTargetGroupArn(backendgroupId) 352 desc := &elbv2.TargetDescription{} 353 desc.SetId(serverId) 354 desc.SetPort(int64(port)) 355 params.SetTargets([]*elbv2.TargetDescription{desc}) 356 _, err = client.DeregisterTargets(params) 357 if err != nil { 358 return errors.Wrap(err, "DeregisterTargets") 359 } 360 361 return nil 362 } 363 364 func (self *SRegion) DeleteElbBackendGroup(backendgroupId string) error { 365 client, err := self.GetElbV2Client() 366 if err != nil { 367 return errors.Wrap(err, "GetElbV2Client") 368 } 369 370 params := &elbv2.DeleteTargetGroupInput{} 371 params.SetTargetGroupArn(backendgroupId) 372 _, err = client.DeleteTargetGroup(params) 373 if err != nil { 374 return errors.Wrap(err, "DeleteTargetGroup") 375 } 376 377 return nil 378 } 379 380 func (self *SRegion) SyncELbBackendGroup(backendgroupId string, group *cloudprovider.SLoadbalancerBackendGroup) error { 381 err := self.modifyELbBackendGroup(backendgroupId, group.HealthCheck) 382 if err != nil { 383 return errors.Wrap(err, "modifyELbBackendGroup") 384 } 385 386 err = self.RemoveElbBackends(backendgroupId) 387 if err != nil { 388 log.Errorf("RemoveElbBackends %s: %s", backendgroupId, err) 389 return errors.Wrap(err, "RemoveElbBackends") 390 } 391 392 return self.AddElbBackends(backendgroupId, group.Backends) 393 } 394 395 func (self *SRegion) modifyELbBackendGroup(backendgroupId string, healthCheck *cloudprovider.SLoadbalancerHealthCheck) error { 396 client, err := self.GetElbV2Client() 397 if err != nil { 398 return err 399 } 400 401 params := &elbv2.ModifyTargetGroupInput{} 402 params.SetTargetGroupArn(backendgroupId) 403 params.SetHealthCheckProtocol(strings.ToUpper(healthCheck.HealthCheckType)) 404 params.SetHealthyThresholdCount(int64(healthCheck.HealthCheckRise)) 405 406 if utils.IsInStringArray(healthCheck.HealthCheckType, []string{api.LB_HEALTH_CHECK_HTTP, api.LB_LISTENER_TYPE_HTTPS}) { 407 params.SetUnhealthyThresholdCount(int64(healthCheck.HealthCheckFail)) 408 params.SetHealthCheckTimeoutSeconds(int64(healthCheck.HealthCheckTimeout)) 409 params.SetHealthCheckIntervalSeconds(int64(healthCheck.HealthCheckInterval)) 410 if len(healthCheck.HealthCheckURI) > 0 { 411 params.SetHealthCheckPath(healthCheck.HealthCheckURI) 412 } 413 414 codes := ToAwsHealthCode(healthCheck.HealthCheckHttpCode) 415 if len(codes) > 0 { 416 matcher := &elbv2.Matcher{} 417 matcher.SetHttpCode(codes) 418 params.SetMatcher(matcher) 419 } 420 } 421 422 _, err = client.ModifyTargetGroup(params) 423 if err != nil { 424 return errors.Wrap(err, "ModifyTargetGroup") 425 } 426 427 return nil 428 } 429 430 func (self *SRegion) RemoveElbBackends(backendgroupId string) error { 431 client, err := self.GetElbV2Client() 432 if err != nil { 433 return err 434 } 435 436 backends, err := self.GetELbBackends(backendgroupId) 437 if err != nil { 438 return errors.Wrap(err, "GetELbBackends") 439 } 440 441 if len(backends) == 0 { 442 return nil 443 } 444 445 targets := []*elbv2.TargetDescription{} 446 for i := range backends { 447 target := &elbv2.TargetDescription{} 448 target.SetId(backends[i].GetBackendId()) 449 target.SetPort(int64(backends[i].GetPort())) 450 targets = append(targets, target) 451 } 452 453 params := &elbv2.DeregisterTargetsInput{} 454 params.SetTargetGroupArn(backendgroupId) 455 params.SetTargets(targets) 456 _, err = client.DeregisterTargets(params) 457 if err != nil { 458 return errors.Wrap(err, "DeregisterTargets") 459 } 460 461 return nil 462 } 463 464 func (self *SRegion) AddElbBackends(backendgroupId string, backends []cloudprovider.SLoadbalancerBackend) error { 465 client, err := self.GetElbV2Client() 466 if err != nil { 467 return err 468 } 469 470 if len(backends) == 0 { 471 return nil 472 } 473 474 params := &elbv2.RegisterTargetsInput{} 475 params.SetTargetGroupArn(backendgroupId) 476 targets := []*elbv2.TargetDescription{} 477 for i := range backends { 478 desc := &elbv2.TargetDescription{} 479 desc.SetId(backends[i].ExternalID) 480 desc.SetPort(int64(backends[i].Port)) 481 targets = append(targets, desc) 482 } 483 484 params.SetTargets(targets) 485 _, err = client.RegisterTargets(params) 486 if err != nil { 487 return errors.Wrap(err, "RegisterTargets") 488 } 489 490 return nil 491 } 492 493 func (self *SRegion) GetElbBackendgroupAttributesById(backendgroupId string) (map[string]string, error) { 494 client, err := self.GetElbV2Client() 495 if err != nil { 496 return nil, errors.Wrap(err, "GetElbV2Client") 497 } 498 499 params := &elbv2.DescribeTargetGroupAttributesInput{} 500 params.SetTargetGroupArn(backendgroupId) 501 502 output, err := client.DescribeTargetGroupAttributes(params) 503 if err != nil { 504 return nil, errors.Wrap(err, "DescribeTargetGroupAttributes") 505 } 506 507 attrs := []map[string]string{} 508 err = unmarshalAwsOutput(output, "Attributes", &attrs) 509 if err != nil { 510 return nil, errors.Wrap(err, "unmarshalAwsOutput.Attributes") 511 } 512 513 ret := map[string]string{} 514 for i := range attrs { 515 for k, v := range attrs[i] { 516 ret[k] = v 517 } 518 } 519 520 return ret, nil 521 }