Skip to content

Commit fd147b4

Browse files
authored
Feature: Implement Multi-Proxy and Load Balancer Strategy Support (gatewayd-io#577)
### Commit Title: Implement Multi-Proxy and Load Balancer Strategy Support This commit introduces significant enhancements to the server configuration by supporting multiple proxies and load balancing strategies. #### Key Changes: - **API Tests**: Updated to reflect the change from a single `Proxy` to a list of `Proxies`. - **Initialization and Configuration**: Modified `run.go` to support multiple proxies and load balancer strategies. - **Configuration Files**: Updated to include fields for multiple proxies and load balancer strategies. - **Global Configuration Validation**: Enhanced for clients, pools, and proxies. - **Load Balancer Configuration**: Added a new `loadBalancer` section in `gatewayd.yaml` for rules and strategies. - **Load Balancing Strategies**: Implemented strategy selection and the Round Robin strategy. - **Testing**: Added tests for load balancer strategies and updated existing tests. - **Error Handling**: Introduced a new error type `ErrorCodeLoadBalancerStrategyNotFound`. - **Proxy Connection Handling**: Improved and added informative comments. #### Configuration Example: - **gatewayd.yaml**: Updated to reflect support for multiple proxies and load balancer strategies. Ensure to update your configuration files accordingly. #### Testing: - Updated existing tests and added new tests for multi-proxy and load balancing functionality. - Verified configuration validation for proxies and load balancers. #### Impact: - Improved flexibility and scalability of server configuration. - Enabled robust proxy management and efficient load distribution. ### Additional Changes: - **Nested Map Structure**: Refactored maps to use nested structures for pools, clients, and proxies. - **Variable Names**: Refactored for consistency and clarity across multiple files. - **YAML Tags**: Added to struct fields for `Client`, `Pool`, and `Proxy` types to ensure compatibility with YAML parsers. - **Configuration Block Names**: Changed from kebab-case to camelCase. - **Tracing**: Avoided using the request context for tracing in the `GetPools` method. - **GetServers API**: Enhanced to include load balancer configuration and updated tests. ### Minor Fixes: - Corrected typos and resolved lint warnings. - Added comments and fixed formatting issues in configuration files. ### Sign-offs and Co-authors: - Co-authored-by: Mostafa Moradian <mstfmoradian@gmail.com> - Signed-off-by: sina <sinadarbouy@gmail.com>
1 parent 18ae198 commit fd147b4

23 files changed

+971
-399
lines changed

api/api.go

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ type API struct {
3838
Options *Options
3939
Config *config.Config
4040
PluginRegistry *plugin.Registry
41-
Pools map[string]*pool.Pool
42-
Proxies map[string]*network.Proxy
41+
Pools map[string]map[string]*pool.Pool
42+
Proxies map[string]map[string]*network.Proxy
4343
Servers map[string]*network.Server
4444
}
4545

@@ -205,12 +205,17 @@ func (a *API) GetPools(context.Context, *emptypb.Empty) (*structpb.Struct, error
205205
_, span := otel.Tracer(config.TracerName).Start(a.ctx, "Get Pools")
206206
defer span.End()
207207

208-
pools := make(map[string]interface{})
209-
for name, p := range a.Pools {
210-
pools[name] = map[string]interface{}{
211-
"cap": p.Cap(),
212-
"size": p.Size(),
208+
pools := make(map[string]any)
209+
210+
for configGroupName, configGroupPools := range a.Pools {
211+
groupPools := make(map[string]any)
212+
for name, p := range configGroupPools {
213+
groupPools[name] = map[string]any{
214+
"cap": p.Cap(),
215+
"size": p.Size(),
216+
}
213217
}
218+
pools[configGroupName] = groupPools
214219
}
215220

216221
poolsConfig, err := structpb.NewStruct(pools)
@@ -231,23 +236,31 @@ func (a *API) GetProxies(context.Context, *emptypb.Empty) (*structpb.Struct, err
231236
_, span := otel.Tracer(config.TracerName).Start(a.ctx, "Get Proxies")
232237
defer span.End()
233238

234-
proxies := make(map[string]interface{})
235-
for name, proxy := range a.Proxies {
236-
available := make([]interface{}, 0)
237-
for _, c := range proxy.AvailableConnectionsString() {
238-
available = append(available, c)
239-
}
239+
// Create a new map to hold the flattened proxies data
240+
proxies := make(map[string]any)
240241

241-
busy := make([]interface{}, 0)
242-
for _, conn := range proxy.BusyConnectionsString() {
243-
busy = append(busy, conn)
244-
}
242+
for configGroupName, configGroupProxies := range a.Proxies {
243+
// Create a map for each configuration group
244+
groupProxies := make(map[string]any)
245+
for name, proxy := range configGroupProxies {
246+
available := make([]any, 0)
247+
for _, c := range proxy.AvailableConnectionsString() {
248+
available = append(available, c)
249+
}
245250

246-
proxies[name] = map[string]interface{}{
247-
"available": available,
248-
"busy": busy,
249-
"total": len(available) + len(busy),
251+
busy := make([]any, 0)
252+
for _, conn := range proxy.BusyConnectionsString() {
253+
busy = append(busy, conn)
254+
}
255+
256+
groupProxies[name] = map[string]any{
257+
"available": available,
258+
"busy": busy,
259+
"total": len(available) + len(busy),
260+
}
250261
}
262+
263+
proxies[configGroupName] = groupProxies
251264
}
252265

253266
proxiesConfig, err := structpb.NewStruct(proxies)
@@ -268,13 +281,14 @@ func (a *API) GetServers(context.Context, *emptypb.Empty) (*structpb.Struct, err
268281
_, span := otel.Tracer(config.TracerName).Start(a.ctx, "Get Servers")
269282
defer span.End()
270283

271-
servers := make(map[string]interface{})
284+
servers := make(map[string]any)
272285
for name, server := range a.Servers {
273-
servers[name] = map[string]interface{}{
286+
servers[name] = map[string]any{
274287
"network": server.Network,
275288
"address": server.Address,
276289
"status": uint(server.Status),
277290
"tickInterval": server.TickInterval.Nanoseconds(),
291+
"loadBalancer": map[string]any{"strategy": server.LoadbalancerStrategyName},
278292
}
279293
}
280294

api/api_helpers_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func getAPIConfig() *API {
4949
context.Background(),
5050
network.Server{
5151
Logger: logger,
52-
Proxy: defaultProxy,
52+
Proxies: []network.IProxy{defaultProxy},
5353
PluginRegistry: pluginReg,
5454
PluginTimeout: config.DefaultPluginTimeout,
5555
Network: "tcp",
@@ -73,11 +73,11 @@ func getAPIConfig() *API {
7373
},
7474
),
7575
PluginRegistry: pluginReg,
76-
Pools: map[string]*pool.Pool{
77-
config.Default: defaultPool,
76+
Pools: map[string]map[string]*pool.Pool{
77+
config.Default: {config.DefaultConfigurationBlock: defaultPool},
7878
},
79-
Proxies: map[string]*network.Proxy{
80-
config.Default: defaultProxy,
79+
Proxies: map[string]map[string]*network.Proxy{
80+
config.Default: {config.DefaultConfigurationBlock: defaultProxy},
8181
},
8282
Servers: servers,
8383
}

api/api_test.go

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -210,21 +210,26 @@ func TestGetPluginsWithEmptyPluginRegistry(t *testing.T) {
210210

211211
func TestPools(t *testing.T) {
212212
api := API{
213-
Pools: map[string]*pool.Pool{
214-
config.Default: pool.NewPool(context.TODO(), config.EmptyPoolCapacity),
213+
Pools: map[string]map[string]*pool.Pool{
214+
config.Default: {config.DefaultConfigurationBlock: pool.NewPool(context.TODO(), config.EmptyPoolCapacity)},
215215
},
216216
ctx: context.Background(),
217217
}
218218
pools, err := api.GetPools(context.Background(), &emptypb.Empty{})
219219
require.NoError(t, err)
220220
assert.NotEmpty(t, pools)
221221
assert.NotEmpty(t, pools.AsMap())
222-
assert.Equal(t, pools.AsMap()[config.Default], map[string]interface{}{"cap": 0.0, "size": 0.0})
222+
223+
assert.Equal(t,
224+
map[string]any{
225+
config.DefaultConfigurationBlock: map[string]any{"cap": 0.0, "size": 0.0},
226+
},
227+
pools.AsMap()[config.Default])
223228
}
224229

225230
func TestPoolsWithEmptyPools(t *testing.T) {
226231
api := API{
227-
Pools: map[string]*pool.Pool{},
232+
Pools: map[string]map[string]*pool.Pool{},
228233
ctx: context.Background(),
229234
}
230235
pools, err := api.GetPools(context.Background(), &emptypb.Empty{})
@@ -258,8 +263,8 @@ func TestGetProxies(t *testing.T) {
258263
)
259264

260265
api := API{
261-
Proxies: map[string]*network.Proxy{
262-
config.Default: proxy,
266+
Proxies: map[string]map[string]*network.Proxy{
267+
config.Default: {config.DefaultConfigurationBlock: proxy},
263268
},
264269
ctx: context.Background(),
265270
}
@@ -268,10 +273,14 @@ func TestGetProxies(t *testing.T) {
268273
assert.NotEmpty(t, proxies)
269274
assert.NotEmpty(t, proxies.AsMap())
270275

271-
if defaultProxy, ok := proxies.AsMap()[config.Default].(map[string]interface{}); ok {
272-
assert.Equal(t, 1.0, defaultProxy["total"])
273-
assert.NotEmpty(t, defaultProxy["available"])
274-
assert.Empty(t, defaultProxy["busy"])
276+
if defaultProxies, ok := proxies.AsMap()[config.Default].(map[string]any); ok {
277+
if defaultProxy, ok := defaultProxies[config.DefaultConfigurationBlock].(map[string]any); ok {
278+
assert.Equal(t, 1.0, defaultProxy["total"])
279+
assert.NotEmpty(t, defaultProxy["available"])
280+
assert.Empty(t, defaultProxy["busy"])
281+
} else {
282+
t.Errorf("proxies.default.%s is not found or not a map", config.DefaultConfigurationBlock)
283+
}
275284
} else {
276285
t.Errorf("proxies.default is not found or not a map")
277286
}
@@ -333,20 +342,21 @@ func TestGetServers(t *testing.T) {
333342
Options: network.Option{
334343
EnableTicker: false,
335344
},
336-
Proxy: proxy,
337-
Logger: zerolog.Logger{},
338-
PluginRegistry: pluginRegistry,
339-
PluginTimeout: config.DefaultPluginTimeout,
340-
HandshakeTimeout: config.DefaultHandshakeTimeout,
345+
Proxies: []network.IProxy{proxy},
346+
Logger: zerolog.Logger{},
347+
PluginRegistry: pluginRegistry,
348+
PluginTimeout: config.DefaultPluginTimeout,
349+
HandshakeTimeout: config.DefaultHandshakeTimeout,
350+
LoadbalancerStrategyName: config.DefaultLoadBalancerStrategy,
341351
},
342352
)
343353

344354
api := API{
345-
Pools: map[string]*pool.Pool{
346-
config.Default: newPool,
355+
Pools: map[string]map[string]*pool.Pool{
356+
config.Default: {config.DefaultConfigurationBlock: newPool},
347357
},
348-
Proxies: map[string]*network.Proxy{
349-
config.Default: proxy,
358+
Proxies: map[string]map[string]*network.Proxy{
359+
config.Default: {config.DefaultConfigurationBlock: proxy},
350360
},
351361
Servers: map[string]*network.Server{
352362
config.Default: server,
@@ -361,12 +371,16 @@ func TestGetServers(t *testing.T) {
361371
if defaultServer, ok := servers.AsMap()[config.Default].(map[string]interface{}); ok {
362372
assert.Equal(t, config.DefaultNetwork, defaultServer["network"])
363373
assert.Equal(t, config.DefaultAddress, "localhost:5432")
364-
status, ok := defaultServer["status"].(float64)
365-
assert.True(t, ok)
366-
assert.Equal(t, config.Stopped, config.Status(status))
367-
tickInterval, ok := defaultServer["tickInterval"].(float64)
368-
assert.True(t, ok)
369-
assert.Equal(t, config.DefaultTickInterval.Nanoseconds(), int64(tickInterval))
374+
statusFloat, isStatusFloat := defaultServer["status"].(float64)
375+
assert.True(t, isStatusFloat, "status should be of type float64")
376+
status := config.Status(statusFloat)
377+
assert.Equal(t, config.Stopped, status)
378+
tickIntervalFloat, isTickIntervalFloat := defaultServer["tickInterval"].(float64)
379+
assert.True(t, isTickIntervalFloat, "tickInterval should be of type float64")
380+
assert.Equal(t, config.DefaultTickInterval.Nanoseconds(), int64(tickIntervalFloat))
381+
loadBalancerMap, isLoadBalancerMap := defaultServer["loadBalancer"].(map[string]interface{})
382+
assert.True(t, isLoadBalancerMap, "loadBalancer should be a map")
383+
assert.Equal(t, config.DefaultLoadBalancerStrategy, loadBalancerMap["strategy"])
370384
} else {
371385
t.Errorf("servers.default is not found or not a map")
372386
}

api/healthcheck_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func Test_Healthchecker(t *testing.T) {
6969
Options: network.Option{
7070
EnableTicker: false,
7171
},
72-
Proxy: proxy,
72+
Proxies: []network.IProxy{proxy},
7373
Logger: zerolog.Logger{},
7474
PluginRegistry: pluginRegistry,
7575
PluginTimeout: config.DefaultPluginTimeout,

0 commit comments

Comments
 (0)