Skip to content

Commit

Permalink
add load balancing to oas configuration (TT-881) (#6830)
Browse files Browse the repository at this point in the history
### **User description**
<details open>
<summary><a href="https://tyktech.atlassian.net/browse/TT-881"
title="TT-881" target="_blank">TT-881</a></summary>
  <br />
  <table>
    <tr>
      <th>Summary</th>
      <td>[OAS] Upstream load balancing</td>
    </tr>
    <tr>
      <th>Type</th>
      <td>
<img alt="Story"
src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10315?size=medium"
/>
        Story
      </td>
    </tr>
    <tr>
      <th>Status</th>
      <td>In Dev</td>
    </tr>
    <tr>
      <th>Points</th>
      <td>N/A</td>
    </tr>
    <tr>
      <th>Labels</th>
<td><a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20A%20ORDER%20BY%20created%20DESC"
title="A">A</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20CSE%20ORDER%20BY%20created%20DESC"
title="CSE">CSE</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20EMEA%20ORDER%20BY%20created%20DESC"
title="EMEA">EMEA</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20Gold%20ORDER%20BY%20created%20DESC"
title="Gold">Gold</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20customer_request%20ORDER%20BY%20created%20DESC"
title="customer_request">customer_request</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20jira_escalated%20ORDER%20BY%20created%20DESC"
title="jira_escalated">jira_escalated</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20updated%20ORDER%20BY%20created%20DESC"
title="updated">updated</a></td>
    </tr>
  </table>
</details>
<!--
  do not remove this marker as it will break jira-lint's functionality.
  added_by_jira_lint
-->

---

This PR adds weighted load balancing to OAS configuration.

## Types of changes

<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Refactoring or add test (improvements in base code or adds test
coverage to functionality)


___

### **PR Type**
Enhancement, Tests


___

### **Description**
- Added load balancing configuration to the OAS upstream definition.

- Implemented `LoadBalancing` and `LoadBalancingTarget` structures with
related methods.

- Updated schema to include load balancing configuration.

- Added comprehensive unit tests for load balancing functionality.


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Tests</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>oas_test.go</strong><dd><code>Remove deprecated load
balancing references in tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

apidef/oas/oas_test.go

<li>Removed references to deprecated load balancing fields.<br> <li>
Cleaned up test cases to align with new load balancing implementation.


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6830/files#diff-74029ee88132d30d6478c96a35f8bb2200e0c8e6f42f2c9b147dc6bb7ce74644">+0/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>upstream_test.go</strong><dd><code>Add unit tests for
load balancing functionality</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

apidef/oas/upstream_test.go

<li>Added unit tests for <code>LoadBalancing</code> and
<code>LoadBalancingTarget</code>.<br> <li> Tested <code>fill</code> and
<code>extractTo</code> methods for load balancing.<br> <li> Verified
behavior with various load balancing configurations.


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6830/files#diff-222cc254c0c6c09fa0cf50087860b837a0873e2aef3c84ec7d80b1014c149057">+192/-0</a>&nbsp;
</td>

</tr>
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>upstream.go</strong><dd><code>Add load balancing logic
to upstream handling</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

apidef/oas/upstream.go

<li>Added <code>LoadBalancing</code> and
<code>LoadBalancingTarget</code> structures.<br> <li> Implemented
methods to populate and extract load balancing <br>configurations.<br>
<li> Integrated load balancing logic into upstream handling.


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6830/files#diff-7b0941c7f37fe5a2a23047e0822a65519ca11c371660f36555b59a60f000e3f4">+104/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>x-tyk-api-gateway.json</strong><dd><code>Update schema
to support load balancing configuration</code>&nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

apidef/oas/schema/x-tyk-api-gateway.json

<li>Updated schema to include <code>loadBalancing</code>
configuration.<br> <li> Added definitions for
<code>X-Tyk-LoadBalancing</code> and
<br><code>X-Tyk-LoadBalancingTarget</code>.


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6830/files#diff-78828969c0c04cc1a776dfc93a8bad3c499a8c83e6169f83e96d090bed3e7dd0">+38/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
  • Loading branch information
pvormste authored Jan 15, 2025
1 parent fcad52f commit 421f64f
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 2 deletions.
2 changes: 0 additions & 2 deletions apidef/oas/oas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,6 @@ func TestOAS_ExtractTo_ResetAPIDefinition(t *testing.T) {
"APIDefinition.UptimeTests.Config.RecheckWait",
"APIDefinition.Proxy.PreserveHostHeader",
"APIDefinition.Proxy.DisableStripSlash",
"APIDefinition.Proxy.EnableLoadBalancing",
"APIDefinition.Proxy.Targets[0]",
"APIDefinition.Proxy.CheckHostAgainstUptimeTests",
"APIDefinition.Proxy.Transport.SSLInsecureSkipVerify",
"APIDefinition.Proxy.Transport.SSLCipherSuites[0]",
Expand Down
38 changes: 38 additions & 0 deletions apidef/oas/schema/x-tyk-api-gateway.json
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,9 @@
},
"authentication": {
"$ref": "#/definitions/X-Tyk-UpstreamAuthentication"
},
"loadBalancing": {
"$ref": "#/definitions/X-Tyk-LoadBalancing"
}
},
"required": [
Expand Down Expand Up @@ -2222,6 +2225,41 @@
}
}
}
},
"X-Tyk-LoadBalancing": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"targets": {
"type": "array",
"items": [
{
"$ref": "#/definitions/X-Tyk-LoadBalancingTarget"
}
]
}
},
"required": [
"enabled",
"targets"
]
},
"X-Tyk-LoadBalancingTarget": {
"type": "object",
"properties": {
"url": {
"type": "string"
},
"weight": {
"type": "number"
}
},
"required": [
"url",
"weight"
]
}
}
}
104 changes: 104 additions & 0 deletions apidef/oas/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ type Upstream struct {

// Authentication contains the configuration related to upstream authentication.
Authentication *UpstreamAuth `bson:"authentication,omitempty" json:"authentication,omitempty"`

// LoadBalancing contains configuration for load balancing between multiple upstream targets.
LoadBalancing *LoadBalancing `bson:"loadBalancing,omitempty" json:"loadBalancing,omitempty"`
}

// Fill fills *Upstream from apidef.APIDefinition.
Expand Down Expand Up @@ -91,6 +94,8 @@ func (u *Upstream) Fill(api apidef.APIDefinition) {
if ShouldOmit(u.Authentication) {
u.Authentication = nil
}

u.fillLoadBalancing(api)
}

// ExtractTo extracts *Upstream into *apidef.APIDefinition.
Expand Down Expand Up @@ -150,6 +155,30 @@ func (u *Upstream) ExtractTo(api *apidef.APIDefinition) {
}

u.Authentication.ExtractTo(&api.UpstreamAuth)

u.loadBalancingExtractTo(api)
}

func (u *Upstream) fillLoadBalancing(api apidef.APIDefinition) {
if u.LoadBalancing == nil {
u.LoadBalancing = &LoadBalancing{}
}

u.LoadBalancing.Fill(api)
if ShouldOmit(u.LoadBalancing) {
u.LoadBalancing = nil
}
}

func (u *Upstream) loadBalancingExtractTo(api *apidef.APIDefinition) {
if u.LoadBalancing == nil {
u.LoadBalancing = &LoadBalancing{}
defer func() {
u.LoadBalancing = nil
}()
}

u.LoadBalancing.ExtractTo(api)
}

// ServiceDiscovery holds configuration required for service discovery.
Expand Down Expand Up @@ -805,3 +834,78 @@ func (u *UpstreamOAuth) ExtractTo(api *apidef.UpstreamOAuth) {
}
u.PasswordAuthentication.ExtractTo(&api.PasswordAuthentication)
}

// LoadBalancing represents the configuration for load balancing between multiple upstream targets.
type LoadBalancing struct {
// Enabled determines if load balancing is active.
Enabled bool `json:"enabled,omitempty" bson:"enabled,omitempty"`
// Targets defines the list of targets with their respective weights for load balancing.
Targets []LoadBalancingTarget `json:"targets,omitempty" bson:"targets,omitempty"`
}

// LoadBalancingTarget represents a single upstream target for load balancing with a URL and an associated weight.
type LoadBalancingTarget struct {
// URL specifies the upstream target URL for load balancing, represented as a string.
URL string `json:"url,omitempty" bson:"url,omitempty"`
// Weight specifies the relative distribution factor for load balancing, determining the importance of this target.
Weight int `json:"weight,omitempty" bson:"weight,omitempty"`
}

// Fill populates the LoadBalancing structure based on the provided APIDefinition, including targets and their weights.
func (l *LoadBalancing) Fill(api apidef.APIDefinition) {
if len(api.Proxy.Targets) == 0 {
api.Proxy.EnableLoadBalancing = false
api.Proxy.Targets = nil
return
}

l.Enabled = api.Proxy.EnableLoadBalancing

targetCounter := make(map[string]*LoadBalancingTarget)
for _, target := range api.Proxy.Targets {
if _, ok := targetCounter[target]; !ok {
targetCounter[target] = &LoadBalancingTarget{
URL: target,
Weight: 0,
}
}
targetCounter[target].Weight++
}

targets := make([]LoadBalancingTarget, len(targetCounter))
i := 0
for _, target := range targetCounter {
targets[i] = *target
i++
}

targetsSorter := func(i, j int) bool {
return targets[i].URL < targets[j].URL
}

sort.Slice(targets, targetsSorter)
l.Targets = targets
}

// ExtractTo populates an APIDefinition's proxy load balancing configuration with data from the LoadBalancing instance.
func (l *LoadBalancing) ExtractTo(api *apidef.APIDefinition) {
if len(l.Targets) == 0 {
api.Proxy.EnableLoadBalancing = false
api.Proxy.Targets = nil
return
}

proxyConfTargets := api.Proxy.Targets
if proxyConfTargets == nil {
proxyConfTargets = make([]string, 0)
}

api.Proxy.EnableLoadBalancing = l.Enabled
for _, target := range l.Targets {
for i := 0; i < target.Weight; i++ {
proxyConfTargets = append(proxyConfTargets, target.URL)
}
}

api.Proxy.Targets = proxyConfTargets
}
192 changes: 192 additions & 0 deletions apidef/oas/upstream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,3 +471,195 @@ func TestCertificatePinning(t *testing.T) {
assert.Equal(t, emptyCertificatePinnning, resultCertificatePinning)
})
}

func TestLoadBalancing(t *testing.T) {
t.Parallel()
t.Run("fill", func(t *testing.T) {
t.Parallel()
testcases := []struct {
title string
input apidef.APIDefinition
expected *LoadBalancing
}{
{
title: "disable load balancing when targets list is empty",
input: apidef.APIDefinition{
Proxy: apidef.ProxyConfig{
EnableLoadBalancing: true,
Targets: []string{},
},
},
expected: nil,
},
{
title: "load balancing disabled with filled target list",
input: apidef.APIDefinition{
Proxy: apidef.ProxyConfig{
EnableLoadBalancing: false,
Targets: []string{
"http://upstream-one",
"http://upstream-one",
"http://upstream-one",
"http://upstream-one",
"http://upstream-one",
"http://upstream-three",
"http://upstream-three",
},
},
},
expected: &LoadBalancing{
Enabled: false,
Targets: []LoadBalancingTarget{
{
URL: "http://upstream-one",
Weight: 5,
},
{
URL: "http://upstream-three",
Weight: 2,
},
},
},
},
{
title: "load balancing enabled with filled target list",
input: apidef.APIDefinition{
Proxy: apidef.ProxyConfig{
EnableLoadBalancing: true,
Targets: []string{
"http://upstream-one",
"http://upstream-one",
"http://upstream-one",
"http://upstream-one",
"http://upstream-one",
"http://upstream-three",
"http://upstream-three",
},
},
},
expected: &LoadBalancing{
Enabled: true,
Targets: []LoadBalancingTarget{
{
URL: "http://upstream-one",
Weight: 5,
},
{
URL: "http://upstream-three",
Weight: 2,
},
},
},
},
}

for _, tc := range testcases {
tc := tc
t.Run(tc.title, func(t *testing.T) {
t.Parallel()

g := new(Upstream)
g.Fill(tc.input)

assert.Equal(t, tc.expected, g.LoadBalancing)
})
}
})

t.Run("extractTo", func(t *testing.T) {
t.Parallel()

testcases := []struct {
title string
input *LoadBalancing
expectedEnabled bool
expectedTargets []string
}{
{
title: "disable load balancing when targets list is empty",
input: &LoadBalancing{
Enabled: false,
Targets: nil,
},
expectedEnabled: false,
expectedTargets: nil,
},
{
title: "load balancing disabled with filled target list",
input: &LoadBalancing{
Enabled: false,
Targets: []LoadBalancingTarget{
{
URL: "http://upstream-one",
Weight: 5,
},
{
URL: "http://upstream-two",
Weight: 0,
},
{
URL: "http://upstream-three",
Weight: 2,
},
},
},
expectedEnabled: false,
expectedTargets: []string{
"http://upstream-one",
"http://upstream-one",
"http://upstream-one",
"http://upstream-one",
"http://upstream-one",
"http://upstream-three",
"http://upstream-three",
},
},
{
title: "load balancing enabled with filled target list",
input: &LoadBalancing{
Enabled: true,
Targets: []LoadBalancingTarget{
{
URL: "http://upstream-one",
Weight: 5,
},
{
URL: "http://upstream-two",
Weight: 0,
},
{
URL: "http://upstream-three",
Weight: 2,
},
},
},
expectedEnabled: true,
expectedTargets: []string{
"http://upstream-one",
"http://upstream-one",
"http://upstream-one",
"http://upstream-one",
"http://upstream-one",
"http://upstream-three",
"http://upstream-three",
},
},
}

for _, tc := range testcases {
tc := tc // Creating a new 'tc' scoped to the loop
t.Run(tc.title, func(t *testing.T) {
t.Parallel()

g := new(Upstream)
g.LoadBalancing = tc.input

var apiDef apidef.APIDefinition
g.ExtractTo(&apiDef)

assert.Equal(t, tc.expectedEnabled, apiDef.Proxy.EnableLoadBalancing)
assert.Equal(t, tc.expectedTargets, apiDef.Proxy.Targets)
})
}
})
}

0 comments on commit 421f64f

Please sign in to comment.