Skip to content

Commit ffec1a5

Browse files
authoredJan 28, 2025··
Update servers improvements (#448)
* Add Content-Type header to NGINX client PATCH requests * Update to reduce unnecessary API calls and sanitize input Update UpdateHTTPServers and UpdateStreamServers: - No longer make extra GET requests for each PUT and DELETE request. - Removes identical duplicate servers. - Returns errors for duplicate servers with different parameters.
1 parent 15b12a8 commit ffec1a5

File tree

3 files changed

+643
-184
lines changed

3 files changed

+643
-184
lines changed
 

‎client/nginx.go

+157-50
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ var (
4848
ErrServerExists = errors.New("server already exists")
4949
ErrNotSupported = errors.New("not supported")
5050
ErrInvalidTimeout = errors.New("invalid timeout")
51+
ErrParameterMismatch = errors.New("encountered duplicate server with different parameters")
5152
ErrPlusVersionNotFound = errors.New("plus version not found in the input string")
5253
)
5354

@@ -775,9 +776,13 @@ func (client *NginxClient) AddHTTPServer(ctx context.Context, upstream string, s
775776
if id != -1 {
776777
return fmt.Errorf("failed to add %v server to %v upstream: %w", server.Server, upstream, ErrServerExists)
777778
}
779+
err = client.addHTTPServer(ctx, upstream, server)
780+
return err
781+
}
778782

783+
func (client *NginxClient) addHTTPServer(ctx context.Context, upstream string, server UpstreamServer) error {
779784
path := fmt.Sprintf("http/upstreams/%v/servers/", upstream)
780-
err = client.post(ctx, path, &server)
785+
err := client.post(ctx, path, &server)
781786
if err != nil {
782787
return fmt.Errorf("failed to add %v server to %v upstream: %w", server.Server, upstream, err)
783788
}
@@ -794,9 +799,13 @@ func (client *NginxClient) DeleteHTTPServer(ctx context.Context, upstream string
794799
if id == -1 {
795800
return fmt.Errorf("failed to remove %v server from %v upstream: %w", server, upstream, ErrServerNotFound)
796801
}
802+
err = client.deleteHTTPServer(ctx, upstream, server, id)
803+
return err
804+
}
797805

798-
path := fmt.Sprintf("http/upstreams/%v/servers/%v", upstream, id)
799-
err = client.delete(ctx, path, http.StatusOK)
806+
func (client *NginxClient) deleteHTTPServer(ctx context.Context, upstream, server string, serverID int) error {
807+
path := fmt.Sprintf("http/upstreams/%v/servers/%v", upstream, serverID)
808+
err := client.delete(ctx, path, http.StatusOK)
800809
if err != nil {
801810
return fmt.Errorf("failed to remove %v server from %v upstream: %w", server, upstream, err)
802811
}
@@ -809,6 +818,8 @@ func (client *NginxClient) DeleteHTTPServer(ctx context.Context, upstream string
809818
// Servers that aren't in the slice, but exist in NGINX, will be removed from NGINX.
810819
// Servers that are in the slice and exist in NGINX, but have different parameters, will be updated.
811820
// The client will attempt to update all servers, returning all the errors that occurred.
821+
// If there are duplicate servers with equivalent parameters, the duplicates will be ignored.
822+
// If there are duplicate servers with different parameters, those server entries will be ignored and an error returned.
812823
func (client *NginxClient) UpdateHTTPServers(ctx context.Context, upstream string, servers []UpstreamServer) (added []UpstreamServer, deleted []UpstreamServer, updated []UpstreamServer, err error) {
813824
serversInNginx, err := client.GetHTTPServers(ctx, upstream)
814825
if err != nil {
@@ -822,10 +833,12 @@ func (client *NginxClient) UpdateHTTPServers(ctx context.Context, upstream strin
822833
formattedServers = append(formattedServers, server)
823834
}
824835

836+
formattedServers, err = deduplicateServers(upstream, formattedServers)
837+
825838
toAdd, toDelete, toUpdate := determineUpdates(formattedServers, serversInNginx)
826839

827840
for _, server := range toAdd {
828-
addErr := client.AddHTTPServer(ctx, upstream, server)
841+
addErr := client.addHTTPServer(ctx, upstream, server)
829842
if addErr != nil {
830843
err = errors.Join(err, addErr)
831844
continue
@@ -834,7 +847,7 @@ func (client *NginxClient) UpdateHTTPServers(ctx context.Context, upstream strin
834847
}
835848

836849
for _, server := range toDelete {
837-
deleteErr := client.DeleteHTTPServer(ctx, upstream, server.Server)
850+
deleteErr := client.deleteHTTPServer(ctx, upstream, server.Server, server.ID)
838851
if deleteErr != nil {
839852
err = errors.Join(err, deleteErr)
840853
continue
@@ -858,46 +871,82 @@ func (client *NginxClient) UpdateHTTPServers(ctx context.Context, upstream strin
858871
return added, deleted, updated, err
859872
}
860873

861-
// haveSameParameters checks if a given server has the same parameters as a server already present in NGINX. Order matters.
862-
func haveSameParameters(newServer UpstreamServer, serverNGX UpstreamServer) bool {
863-
newServer.ID = serverNGX.ID
874+
func deduplicateServers(upstream string, servers []UpstreamServer) ([]UpstreamServer, error) {
875+
type serverCheck struct {
876+
server UpstreamServer
877+
valid bool
878+
}
864879

865-
if serverNGX.MaxConns != nil && newServer.MaxConns == nil {
866-
newServer.MaxConns = &defaultMaxConns
880+
serverMap := make(map[string]*serverCheck, len(servers))
881+
var err error
882+
for _, server := range servers {
883+
if prev, ok := serverMap[server.Server]; ok {
884+
if !prev.valid {
885+
continue
886+
}
887+
if !server.hasSameParametersAs(prev.server) {
888+
prev.valid = false
889+
err = errors.Join(err, fmt.Errorf(
890+
"failed to update %s server to %s upstream: %w",
891+
server.Server, upstream, ErrParameterMismatch))
892+
}
893+
continue
894+
}
895+
serverMap[server.Server] = &serverCheck{server, true}
867896
}
897+
retServers := make([]UpstreamServer, 0, len(serverMap))
898+
for _, server := range servers {
899+
if check, ok := serverMap[server.Server]; ok && check.valid {
900+
retServers = append(retServers, server)
901+
delete(serverMap, server.Server)
902+
}
903+
}
904+
return retServers, err
905+
}
868906

869-
if serverNGX.MaxFails != nil && newServer.MaxFails == nil {
870-
newServer.MaxFails = &defaultMaxFails
907+
// hasSameParametersAs checks if a given server has the same parameters.
908+
func (s UpstreamServer) hasSameParametersAs(compareServer UpstreamServer) bool {
909+
s.ID = compareServer.ID
910+
s.applyDefaults()
911+
compareServer.applyDefaults()
912+
return reflect.DeepEqual(s, compareServer)
913+
}
914+
915+
func (s *UpstreamServer) applyDefaults() {
916+
if s.MaxConns == nil {
917+
s.MaxConns = &defaultMaxConns
871918
}
872919

873-
if serverNGX.FailTimeout != "" && newServer.FailTimeout == "" {
874-
newServer.FailTimeout = defaultFailTimeout
920+
if s.MaxFails == nil {
921+
s.MaxFails = &defaultMaxFails
875922
}
876923

877-
if serverNGX.SlowStart != "" && newServer.SlowStart == "" {
878-
newServer.SlowStart = defaultSlowStart
924+
if s.FailTimeout == "" {
925+
s.FailTimeout = defaultFailTimeout
879926
}
880927

881-
if serverNGX.Backup != nil && newServer.Backup == nil {
882-
newServer.Backup = &defaultBackup
928+
if s.SlowStart == "" {
929+
s.SlowStart = defaultSlowStart
883930
}
884931

885-
if serverNGX.Down != nil && newServer.Down == nil {
886-
newServer.Down = &defaultDown
932+
if s.Backup == nil {
933+
s.Backup = &defaultBackup
887934
}
888935

889-
if serverNGX.Weight != nil && newServer.Weight == nil {
890-
newServer.Weight = &defaultWeight
936+
if s.Down == nil {
937+
s.Down = &defaultDown
891938
}
892939

893-
return reflect.DeepEqual(newServer, serverNGX)
940+
if s.Weight == nil {
941+
s.Weight = &defaultWeight
942+
}
894943
}
895944

896945
func determineUpdates(updatedServers []UpstreamServer, nginxServers []UpstreamServer) (toAdd []UpstreamServer, toRemove []UpstreamServer, toUpdate []UpstreamServer) {
897946
for _, server := range updatedServers {
898947
updateFound := false
899948
for _, serverNGX := range nginxServers {
900-
if server.Server == serverNGX.Server && !haveSameParameters(server, serverNGX) {
949+
if server.Server == serverNGX.Server && !server.hasSameParametersAs(serverNGX) {
901950
server.ID = serverNGX.ID
902951
updateFound = true
903952
break
@@ -1046,6 +1095,7 @@ func (client *NginxClient) patch(ctx context.Context, path string, input interfa
10461095
if err != nil {
10471096
return fmt.Errorf("failed to create a patch request: %w", err)
10481097
}
1098+
req.Header.Set("Content-Type", "application/json")
10491099

10501100
resp, err := client.httpClient.Do(req)
10511101
if err != nil {
@@ -1088,9 +1138,13 @@ func (client *NginxClient) AddStreamServer(ctx context.Context, upstream string,
10881138
if id != -1 {
10891139
return fmt.Errorf("failed to add %v stream server to %v upstream: %w", server.Server, upstream, ErrServerExists)
10901140
}
1141+
err = client.addStreamServer(ctx, upstream, server)
1142+
return err
1143+
}
10911144

1145+
func (client *NginxClient) addStreamServer(ctx context.Context, upstream string, server StreamUpstreamServer) error {
10921146
path := fmt.Sprintf("stream/upstreams/%v/servers/", upstream)
1093-
err = client.post(ctx, path, &server)
1147+
err := client.post(ctx, path, &server)
10941148
if err != nil {
10951149
return fmt.Errorf("failed to add %v stream server to %v upstream: %w", server.Server, upstream, err)
10961150
}
@@ -1106,9 +1160,13 @@ func (client *NginxClient) DeleteStreamServer(ctx context.Context, upstream stri
11061160
if id == -1 {
11071161
return fmt.Errorf("failed to remove %v stream server from %v upstream: %w", server, upstream, ErrServerNotFound)
11081162
}
1163+
err = client.deleteStreamServer(ctx, upstream, server, id)
1164+
return err
1165+
}
11091166

1110-
path := fmt.Sprintf("stream/upstreams/%v/servers/%v", upstream, id)
1111-
err = client.delete(ctx, path, http.StatusOK)
1167+
func (client *NginxClient) deleteStreamServer(ctx context.Context, upstream, server string, serverID int) error {
1168+
path := fmt.Sprintf("stream/upstreams/%v/servers/%v", upstream, serverID)
1169+
err := client.delete(ctx, path, http.StatusOK)
11121170
if err != nil {
11131171
return fmt.Errorf("failed to remove %v stream server from %v upstream: %w", server, upstream, err)
11141172
}
@@ -1120,6 +1178,8 @@ func (client *NginxClient) DeleteStreamServer(ctx context.Context, upstream stri
11201178
// Servers that aren't in the slice, but exist in NGINX, will be removed from NGINX.
11211179
// Servers that are in the slice and exist in NGINX, but have different parameters, will be updated.
11221180
// The client will attempt to update all servers, returning all the errors that occurred.
1181+
// If there are duplicate servers with equivalent parameters, the duplicates will be ignored.
1182+
// If there are duplicate servers with different parameters, those server entries will be ignored and an error returned.
11231183
func (client *NginxClient) UpdateStreamServers(ctx context.Context, upstream string, servers []StreamUpstreamServer) (added []StreamUpstreamServer, deleted []StreamUpstreamServer, updated []StreamUpstreamServer, err error) {
11241184
serversInNginx, err := client.GetStreamServers(ctx, upstream)
11251185
if err != nil {
@@ -1132,10 +1192,12 @@ func (client *NginxClient) UpdateStreamServers(ctx context.Context, upstream str
11321192
formattedServers = append(formattedServers, server)
11331193
}
11341194

1195+
formattedServers, err = deduplicateStreamServers(upstream, formattedServers)
1196+
11351197
toAdd, toDelete, toUpdate := determineStreamUpdates(formattedServers, serversInNginx)
11361198

11371199
for _, server := range toAdd {
1138-
addErr := client.AddStreamServer(ctx, upstream, server)
1200+
addErr := client.addStreamServer(ctx, upstream, server)
11391201
if addErr != nil {
11401202
err = errors.Join(err, addErr)
11411203
continue
@@ -1144,7 +1206,7 @@ func (client *NginxClient) UpdateStreamServers(ctx context.Context, upstream str
11441206
}
11451207

11461208
for _, server := range toDelete {
1147-
deleteErr := client.DeleteStreamServer(ctx, upstream, server.Server)
1209+
deleteErr := client.deleteStreamServer(ctx, upstream, server.Server, server.ID)
11481210
if deleteErr != nil {
11491211
err = errors.Join(err, deleteErr)
11501212
continue
@@ -1183,45 +1245,82 @@ func (client *NginxClient) getIDOfStreamServer(ctx context.Context, upstream str
11831245
return -1, nil
11841246
}
11851247

1186-
// haveSameParametersForStream checks if a given server has the same parameters as a server already present in NGINX. Order matters.
1187-
func haveSameParametersForStream(newServer StreamUpstreamServer, serverNGX StreamUpstreamServer) bool {
1188-
newServer.ID = serverNGX.ID
1189-
if serverNGX.MaxConns != nil && newServer.MaxConns == nil {
1190-
newServer.MaxConns = &defaultMaxConns
1248+
func deduplicateStreamServers(upstream string, servers []StreamUpstreamServer) ([]StreamUpstreamServer, error) {
1249+
type serverCheck struct {
1250+
server StreamUpstreamServer
1251+
valid bool
11911252
}
11921253

1193-
if serverNGX.MaxFails != nil && newServer.MaxFails == nil {
1194-
newServer.MaxFails = &defaultMaxFails
1254+
serverMap := make(map[string]*serverCheck, len(servers))
1255+
var err error
1256+
for _, server := range servers {
1257+
if prev, ok := serverMap[server.Server]; ok {
1258+
if !prev.valid {
1259+
continue
1260+
}
1261+
if !server.hasSameParametersAs(prev.server) {
1262+
prev.valid = false
1263+
err = errors.Join(err, fmt.Errorf(
1264+
"failed to update stream %s server to %s upstream: %w",
1265+
server.Server, upstream, ErrParameterMismatch))
1266+
}
1267+
continue
1268+
}
1269+
serverMap[server.Server] = &serverCheck{server, true}
11951270
}
1271+
retServers := make([]StreamUpstreamServer, 0, len(serverMap))
1272+
for _, server := range servers {
1273+
if check, ok := serverMap[server.Server]; ok && check.valid {
1274+
retServers = append(retServers, server)
1275+
delete(serverMap, server.Server)
1276+
}
1277+
}
1278+
return retServers, err
1279+
}
11961280

1197-
if serverNGX.FailTimeout != "" && newServer.FailTimeout == "" {
1198-
newServer.FailTimeout = defaultFailTimeout
1281+
// hasSameParametersAs checks if a given server has the same parameters.
1282+
func (s StreamUpstreamServer) hasSameParametersAs(compareServer StreamUpstreamServer) bool {
1283+
s.ID = compareServer.ID
1284+
s.applyDefaults()
1285+
compareServer.applyDefaults()
1286+
return reflect.DeepEqual(s, compareServer)
1287+
}
1288+
1289+
func (s *StreamUpstreamServer) applyDefaults() {
1290+
if s.MaxConns == nil {
1291+
s.MaxConns = &defaultMaxConns
1292+
}
1293+
1294+
if s.MaxFails == nil {
1295+
s.MaxFails = &defaultMaxFails
11991296
}
12001297

1201-
if serverNGX.SlowStart != "" && newServer.SlowStart == "" {
1202-
newServer.SlowStart = defaultSlowStart
1298+
if s.FailTimeout == "" {
1299+
s.FailTimeout = defaultFailTimeout
12031300
}
12041301

1205-
if serverNGX.Backup != nil && newServer.Backup == nil {
1206-
newServer.Backup = &defaultBackup
1302+
if s.SlowStart == "" {
1303+
s.SlowStart = defaultSlowStart
12071304
}
12081305

1209-
if serverNGX.Down != nil && newServer.Down == nil {
1210-
newServer.Down = &defaultDown
1306+
if s.Backup == nil {
1307+
s.Backup = &defaultBackup
12111308
}
12121309

1213-
if serverNGX.Weight != nil && newServer.Weight == nil {
1214-
newServer.Weight = &defaultWeight
1310+
if s.Down == nil {
1311+
s.Down = &defaultDown
12151312
}
12161313

1217-
return reflect.DeepEqual(newServer, serverNGX)
1314+
if s.Weight == nil {
1315+
s.Weight = &defaultWeight
1316+
}
12181317
}
12191318

12201319
func determineStreamUpdates(updatedServers []StreamUpstreamServer, nginxServers []StreamUpstreamServer) (toAdd []StreamUpstreamServer, toRemove []StreamUpstreamServer, toUpdate []StreamUpstreamServer) {
12211320
for _, server := range updatedServers {
12221321
updateFound := false
12231322
for _, serverNGX := range nginxServers {
1224-
if server.Server == serverNGX.Server && !haveSameParametersForStream(server, serverNGX) {
1323+
if server.Server == serverNGX.Server && !server.hasSameParametersAs(serverNGX) {
12251324
server.ID = serverNGX.ID
12261325
updateFound = true
12271326
break
@@ -1949,9 +2048,13 @@ func (client *NginxClient) deleteKeyValPairs(ctx context.Context, zone string, s
19492048
return nil
19502049
}
19512050

1952-
// UpdateHTTPServer updates the server of the upstream.
2051+
// UpdateHTTPServer updates the server of the upstream with the matching server ID.
19532052
func (client *NginxClient) UpdateHTTPServer(ctx context.Context, upstream string, server UpstreamServer) error {
19542053
path := fmt.Sprintf("http/upstreams/%v/servers/%v", upstream, server.ID)
2054+
// The server ID is expected in the URI, but not expected in the body.
2055+
// The NGINX API will return
2056+
// {"error":{"status":400,"text":"unknown parameter \"id\"","code":"UpstreamConfFormatError"}
2057+
// if the ID field is present.
19552058
server.ID = 0
19562059
err := client.patch(ctx, path, &server, http.StatusOK)
19572060
if err != nil {
@@ -1961,9 +2064,13 @@ func (client *NginxClient) UpdateHTTPServer(ctx context.Context, upstream string
19612064
return nil
19622065
}
19632066

1964-
// UpdateStreamServer updates the stream server of the upstream.
2067+
// UpdateStreamServer updates the stream server of the upstream with the matching server ID.
19652068
func (client *NginxClient) UpdateStreamServer(ctx context.Context, upstream string, server StreamUpstreamServer) error {
19662069
path := fmt.Sprintf("stream/upstreams/%v/servers/%v", upstream, server.ID)
2070+
// The server ID is expected in the URI, but not expected in the body.
2071+
// The NGINX API will return
2072+
// {"error":{"status":400,"text":"unknown parameter \"id\"","code":"UpstreamConfFormatError"}
2073+
// if the ID field is present.
19672074
server.ID = 0
19682075
err := client.patch(ctx, path, &server, http.StatusOK)
19692076
if err != nil {

‎client/nginx_test.go

+420-130
Original file line numberDiff line numberDiff line change
@@ -477,9 +477,9 @@ func TestHaveSameParameters(t *testing.T) {
477477
for _, test := range tests {
478478
t.Run(test.msg, func(t *testing.T) {
479479
t.Parallel()
480-
result := haveSameParameters(test.server, test.serverNGX)
480+
result := test.server.hasSameParametersAs(test.serverNGX)
481481
if result != test.expected {
482-
t.Errorf("haveSameParameters(%v, %v) returned %v but expected %v", test.server, test.serverNGX, result, test.expected)
482+
t.Errorf("(%v) hasSameParametersAs (%v) returned %v but expected %v", test.server, test.serverNGX, result, test.expected)
483483
}
484484
})
485485
}
@@ -562,9 +562,9 @@ func TestHaveSameParametersForStream(t *testing.T) {
562562
for _, test := range tests {
563563
t.Run(test.msg, func(t *testing.T) {
564564
t.Parallel()
565-
result := haveSameParametersForStream(test.server, test.serverNGX)
565+
result := test.server.hasSameParametersAs(test.serverNGX)
566566
if result != test.expected {
567-
t.Errorf("haveSameParametersForStream(%v, %v) returned %v but expected %v", test.server, test.serverNGX, result, test.expected)
567+
t.Errorf("(%v) hasSameParametersAs (%v) returned %v but expected %v", test.server, test.serverNGX, result, test.expected)
568568
}
569569
})
570570
}
@@ -982,174 +982,464 @@ func TestExtractPlusVersionNegativeCase(t *testing.T) {
982982
}
983983
}
984984

985-
func TestClientHTTPUpdateServers(t *testing.T) {
985+
func TestUpdateHTTPServers(t *testing.T) {
986986
t.Parallel()
987987

988-
responses := []response{
989-
// response for first serversInNginx GET servers
990-
{
991-
statusCode: http.StatusOK,
992-
servers: []UpstreamServer{},
988+
testcases := map[string]struct {
989+
reqServers []UpstreamServer
990+
responses []response
991+
expAdded, expDeleted, expUpdated int
992+
expErr bool
993+
}{
994+
"successfully add 1 server": {
995+
reqServers: []UpstreamServer{{Server: "127.0.0.1:80"}},
996+
responses: []response{
997+
// response for first serversInNginx GET servers
998+
{
999+
statusCode: http.StatusOK,
1000+
},
1001+
// response for addHTTPServer POST server for http server
1002+
{
1003+
statusCode: http.StatusCreated,
1004+
},
1005+
},
1006+
expAdded: 1,
9931007
},
994-
// response for AddHTTPServer GET servers for http server
995-
{
996-
statusCode: http.StatusOK,
997-
servers: []UpstreamServer{},
1008+
"successfully update 1 server": {
1009+
reqServers: []UpstreamServer{{Server: "127.0.0.1:80"}},
1010+
responses: []response{
1011+
// response for first serversInNginx GET servers
1012+
{
1013+
statusCode: http.StatusOK,
1014+
servers: []UpstreamServer{
1015+
{ID: 1, Server: "127.0.0.1:80", Route: "/test"},
1016+
},
1017+
},
1018+
// response for UpdateHTTPServer PATCH server for http server
1019+
{
1020+
statusCode: http.StatusOK,
1021+
},
1022+
},
1023+
expUpdated: 1,
9981024
},
999-
// response for AddHTTPServer POST server for http server
1000-
{
1001-
statusCode: http.StatusInternalServerError,
1002-
servers: []UpstreamServer{},
1025+
"successfully delete 1 server": {
1026+
reqServers: []UpstreamServer{{Server: "127.0.0.1:80"}},
1027+
responses: []response{
1028+
// response for first serversInNginx GET servers
1029+
{
1030+
statusCode: http.StatusOK,
1031+
servers: []UpstreamServer{
1032+
{ID: 1, Server: "127.0.0.1:80"},
1033+
{ID: 2, Server: "127.0.0.2:80"},
1034+
},
1035+
},
1036+
// response for deleteHTTPServer DELETE server for http server
1037+
{
1038+
statusCode: http.StatusOK,
1039+
},
1040+
},
1041+
expDeleted: 1,
10031042
},
1004-
// response for AddHTTPServer GET servers for https server
1005-
{
1006-
statusCode: http.StatusOK,
1007-
servers: []UpstreamServer{},
1043+
"successfully add 1 server, update 1 server, delete 1 server": {
1044+
reqServers: []UpstreamServer{
1045+
{Server: "127.0.0.1:80", Route: "/test"},
1046+
{Server: "127.0.0.2:80"},
1047+
},
1048+
responses: []response{
1049+
// response for first serversInNginx GET servers
1050+
{
1051+
statusCode: http.StatusOK,
1052+
servers: []UpstreamServer{
1053+
{ID: 1, Server: "127.0.0.1:80"},
1054+
{ID: 2, Server: "127.0.0.3:80"},
1055+
},
1056+
},
1057+
// response for addHTTPServer POST server for http server
1058+
{
1059+
statusCode: http.StatusCreated,
1060+
},
1061+
// response for deleteHTTPServer DELETE server for http server
1062+
{
1063+
statusCode: http.StatusOK,
1064+
},
1065+
// response for UpdateHTTPServer PATCH server for http server
1066+
{
1067+
statusCode: http.StatusOK,
1068+
},
1069+
},
1070+
expAdded: 1,
1071+
expUpdated: 1,
1072+
expDeleted: 1,
10081073
},
1009-
// response for AddHTTPServer POST server for https server
1010-
{
1011-
statusCode: http.StatusCreated,
1012-
servers: []UpstreamServer{},
1074+
"successfully add 1 server with ignored identical duplicate": {
1075+
reqServers: []UpstreamServer{
1076+
{Server: "127.0.0.1:80", Route: "/test"},
1077+
{Server: "127.0.0.1", Route: "/test"},
1078+
{Server: "127.0.0.1:80", Route: "/test", MaxConns: &defaultMaxConns},
1079+
{Server: "127.0.0.1:80", Route: "/test", Backup: &defaultBackup},
1080+
{Server: "127.0.0.1", Route: "/test", SlowStart: defaultSlowStart},
1081+
},
1082+
responses: []response{
1083+
// response for first serversInNginx GET servers
1084+
{
1085+
statusCode: http.StatusOK,
1086+
servers: []UpstreamServer{},
1087+
},
1088+
// response for addHTTPServer POST server for http server
1089+
{
1090+
statusCode: http.StatusCreated,
1091+
},
1092+
},
1093+
expAdded: 1,
1094+
},
1095+
"successfully add 1 server, receive 1 error for non-identical duplicates": {
1096+
reqServers: []UpstreamServer{
1097+
{Server: "127.0.0.1:80", Route: "/test"},
1098+
{Server: "127.0.0.1:80", Route: "/test"},
1099+
{Server: "127.0.0.2:80", Route: "/test1"},
1100+
{Server: "127.0.0.2:80", Route: "/test2"},
1101+
{Server: "127.0.0.2:80", Route: "/test3"},
1102+
},
1103+
responses: []response{
1104+
// response for first serversInNginx GET servers
1105+
{
1106+
statusCode: http.StatusOK,
1107+
servers: []UpstreamServer{},
1108+
},
1109+
// response for addHTTPServer POST server for http server
1110+
{
1111+
statusCode: http.StatusCreated,
1112+
},
1113+
},
1114+
expAdded: 1,
1115+
expErr: true,
1116+
},
1117+
"successfully add 1 server, receive 1 error": {
1118+
reqServers: []UpstreamServer{
1119+
{Server: "127.0.0.1:80"},
1120+
{Server: "127.0.0.1:443"},
1121+
},
1122+
responses: []response{ // response for first serversInNginx GET servers
1123+
{
1124+
statusCode: http.StatusOK,
1125+
servers: []UpstreamServer{},
1126+
},
1127+
// response for addHTTPServer POST server for server1
1128+
{
1129+
statusCode: http.StatusInternalServerError,
1130+
servers: []UpstreamServer{},
1131+
},
1132+
// response for addHTTPServer POST server for server2
1133+
{
1134+
statusCode: http.StatusCreated,
1135+
servers: []UpstreamServer{},
1136+
},
1137+
},
1138+
expAdded: 1,
1139+
expErr: true,
10131140
},
10141141
}
10151142

1016-
handler := &fakeHandler{
1017-
func(w http.ResponseWriter, _ *http.Request) {
1018-
if len(responses) == 0 {
1019-
t.Fatal("ran out of responses")
1020-
}
1143+
for name, tc := range testcases {
1144+
t.Run(name, func(t *testing.T) {
1145+
t.Parallel()
10211146

1022-
re := responses[0]
1023-
responses = responses[1:]
1147+
var requests []*http.Request
1148+
handler := &fakeHandler{
1149+
func(w http.ResponseWriter, r *http.Request) {
1150+
requests = append(requests, r)
10241151

1025-
w.WriteHeader(re.statusCode)
1152+
if len(tc.responses) == 0 {
1153+
t.Fatal("ran out of responses")
1154+
}
1155+
if r.Method == http.MethodPost || r.Method == http.MethodPut {
1156+
contentType, ok := r.Header["Content-Type"]
1157+
if !ok {
1158+
t.Fatalf("expected request type %s to have a Content-Type header", r.Method)
1159+
}
1160+
if len(contentType) != 1 || contentType[0] != "application/json" {
1161+
t.Fatalf("expected request type %s to have a Content-Type header value of 'application/json'", r.Method)
1162+
}
1163+
}
10261164

1027-
resp, err := json.Marshal(re.servers)
1028-
if err != nil {
1029-
t.Fatal(err)
1030-
}
1031-
_, err = w.Write(resp)
1032-
if err != nil {
1033-
t.Fatal(err)
1034-
}
1035-
},
1036-
}
1165+
re := tc.responses[0]
1166+
tc.responses = tc.responses[1:]
10371167

1038-
server := httptest.NewServer(handler)
1039-
defer server.Close()
1168+
w.WriteHeader(re.statusCode)
10401169

1041-
client, err := NewNginxClient(server.URL, WithHTTPClient(&http.Client{}))
1042-
if err != nil {
1043-
t.Fatal(err)
1044-
}
1170+
resp, err := json.Marshal(re.servers)
1171+
if err != nil {
1172+
t.Fatal(err)
1173+
}
1174+
_, err = w.Write(resp)
1175+
if err != nil {
1176+
t.Fatal(err)
1177+
}
1178+
},
1179+
}
10451180

1046-
httpServer := UpstreamServer{Server: "127.0.0.1:80"}
1047-
httpsServer := UpstreamServer{Server: "127.0.0.1:443"}
1181+
server := httptest.NewServer(handler)
1182+
defer server.Close()
10481183

1049-
// we expect that we will get an error for the 500 error encountered when putting the http server
1050-
// but we also expect that we have the https server added
1051-
added, _, _, err := client.UpdateHTTPServers(context.TODO(), "fakeUpstream", []UpstreamServer{
1052-
httpServer,
1053-
httpsServer,
1054-
})
1055-
if err == nil {
1056-
t.Fatal("expected to receive an error for 500 response when adding first server")
1057-
}
1184+
client, err := NewNginxClient(server.URL, WithHTTPClient(&http.Client{}))
1185+
if err != nil {
1186+
t.Fatal(err)
1187+
}
10581188

1059-
if len(added) != 1 {
1060-
t.Fatalf("expected to get one added server, instead got %d", len(added))
1061-
}
1189+
added, deleted, updated, err := client.UpdateHTTPServers(context.Background(), "fakeUpstream", tc.reqServers)
1190+
if tc.expErr && err == nil {
1191+
t.Fatal("expected to receive an error")
1192+
}
1193+
if !tc.expErr && err != nil {
1194+
t.Fatalf("received an unexpected error: %v", err)
1195+
}
10621196

1063-
if !reflect.DeepEqual(httpsServer, added[0]) {
1064-
t.Errorf("expected: %v got: %v", httpsServer, added[0])
1197+
if len(added) != tc.expAdded {
1198+
t.Fatalf("expected to get %d added server(s), instead got %d", tc.expAdded, len(added))
1199+
}
1200+
if len(deleted) != tc.expDeleted {
1201+
t.Fatalf("expected to get %d deleted server(s), instead got %d", tc.expDeleted, len(deleted))
1202+
}
1203+
if len(updated) != tc.expUpdated {
1204+
t.Fatalf("expected to get %d updated server(s), instead got %d", tc.expUpdated, len(updated))
1205+
}
1206+
if len(tc.responses) != 0 {
1207+
t.Fatalf("did not use all expected responses, %d unused", len(tc.responses))
1208+
}
1209+
})
10651210
}
10661211
}
10671212

1068-
func TestClientStreamUpdateServers(t *testing.T) {
1213+
func TestUpdateStreamServers(t *testing.T) {
10691214
t.Parallel()
10701215

1071-
responses := []response{
1072-
// response for first serversInNginx GET servers
1073-
{
1074-
statusCode: http.StatusOK,
1075-
servers: []UpstreamServer{},
1216+
testcases := map[string]struct {
1217+
reqServers []StreamUpstreamServer
1218+
responses []response
1219+
expAdded, expDeleted, expUpdated int
1220+
expErr bool
1221+
}{
1222+
"successfully add 1 server": {
1223+
reqServers: []StreamUpstreamServer{{Server: "127.0.0.1:80"}},
1224+
responses: []response{
1225+
// response for first serversInNginx GET servers
1226+
{
1227+
statusCode: http.StatusOK,
1228+
},
1229+
// response for addStreamServer POST server for stream server
1230+
{
1231+
statusCode: http.StatusCreated,
1232+
},
1233+
},
1234+
expAdded: 1,
10761235
},
1077-
// response for AddStreamServer GET servers for streamServer1
1078-
{
1079-
statusCode: http.StatusOK,
1080-
servers: []UpstreamServer{},
1236+
"successfully update 1 server": {
1237+
reqServers: []StreamUpstreamServer{{Server: "127.0.0.1:80"}},
1238+
responses: []response{
1239+
// response for first serversInNginx GET servers
1240+
{
1241+
statusCode: http.StatusOK,
1242+
servers: []StreamUpstreamServer{
1243+
{ID: 1, Server: "127.0.0.1:80", SlowStart: "30s"},
1244+
},
1245+
},
1246+
// response for UpdateStreamServer PATCH server for stream server
1247+
{
1248+
statusCode: http.StatusOK,
1249+
},
1250+
},
1251+
expUpdated: 1,
10811252
},
1082-
// response for AddStreamServer POST server for streamServer1
1083-
{
1084-
statusCode: http.StatusInternalServerError,
1085-
servers: []UpstreamServer{},
1253+
"successfully delete 1 server": {
1254+
reqServers: []StreamUpstreamServer{{Server: "127.0.0.1:80"}},
1255+
responses: []response{
1256+
// response for first serversInNginx GET servers
1257+
{
1258+
statusCode: http.StatusOK,
1259+
servers: []StreamUpstreamServer{
1260+
{ID: 1, Server: "127.0.0.1:80"},
1261+
{ID: 2, Server: "127.0.0.2:80"},
1262+
},
1263+
},
1264+
// response for deleteStreamServer DELETE server for stream server
1265+
{
1266+
statusCode: http.StatusOK,
1267+
},
1268+
},
1269+
expDeleted: 1,
10861270
},
1087-
// response for AddStreamServer GET servers for streamServer2
1088-
{
1089-
statusCode: http.StatusOK,
1090-
servers: []UpstreamServer{},
1271+
"successfully add 1 server, update 1 server, delete 1 server": {
1272+
reqServers: []StreamUpstreamServer{
1273+
{Server: "127.0.0.1:80", SlowStart: "30s"},
1274+
{Server: "127.0.0.2:80"},
1275+
},
1276+
responses: []response{
1277+
// response for first serversInNginx GET servers
1278+
{
1279+
statusCode: http.StatusOK,
1280+
servers: []StreamUpstreamServer{
1281+
{ID: 1, Server: "127.0.0.1:80"},
1282+
{ID: 2, Server: "127.0.0.3:80"},
1283+
},
1284+
},
1285+
// response for addStreamServer POST server for stream server
1286+
{
1287+
statusCode: http.StatusCreated,
1288+
},
1289+
// response for deleteStreamServer DELETE server for stream server
1290+
{
1291+
statusCode: http.StatusOK,
1292+
},
1293+
// response for UpdateStreamServer PATCH server for stream server
1294+
{
1295+
statusCode: http.StatusOK,
1296+
},
1297+
},
1298+
expAdded: 1,
1299+
expUpdated: 1,
1300+
expDeleted: 1,
10911301
},
1092-
// response for AddStreamServer POST server for streamServer2
1093-
{
1094-
statusCode: http.StatusCreated,
1095-
servers: []UpstreamServer{},
1302+
"successfully add 1 server with ignored identical duplicate": {
1303+
reqServers: []StreamUpstreamServer{
1304+
{Server: "127.0.0.1:80", SlowStart: "30s"},
1305+
{Server: "127.0.0.1", SlowStart: "30s"},
1306+
{Server: "127.0.0.1:80", SlowStart: "30s", MaxConns: &defaultMaxConns},
1307+
{Server: "127.0.0.1", SlowStart: "30s", MaxFails: &defaultMaxFails},
1308+
{Server: "127.0.0.1", SlowStart: "30s", FailTimeout: defaultFailTimeout},
1309+
},
1310+
responses: []response{
1311+
// response for first serversInNginx GET servers
1312+
{
1313+
statusCode: http.StatusOK,
1314+
servers: []UpstreamServer{},
1315+
},
1316+
// response for addStreamServer POST server for stream server
1317+
{
1318+
statusCode: http.StatusCreated,
1319+
},
1320+
},
1321+
expAdded: 1,
1322+
},
1323+
"successfully add 1 server, receive 1 error for non-identical duplicates": {
1324+
reqServers: []StreamUpstreamServer{
1325+
{Server: "127.0.0.1:80", SlowStart: "30s"},
1326+
{Server: "127.0.0.1:80", SlowStart: "30s"},
1327+
{Server: "127.0.0.2:80", SlowStart: "10s"},
1328+
{Server: "127.0.0.2:80", SlowStart: "20s"},
1329+
{Server: "127.0.0.2:80", SlowStart: "30s"},
1330+
},
1331+
responses: []response{
1332+
// response for first serversInNginx GET servers
1333+
{
1334+
statusCode: http.StatusOK,
1335+
servers: []UpstreamServer{},
1336+
},
1337+
// response for addStreamServer POST server for stream server
1338+
{
1339+
statusCode: http.StatusCreated,
1340+
},
1341+
},
1342+
expAdded: 1,
1343+
expErr: true,
1344+
},
1345+
"successfully add 1 server, receive 1 error": {
1346+
reqServers: []StreamUpstreamServer{
1347+
{Server: "127.0.0.1:2000"},
1348+
{Server: "127.0.0.1:3000"},
1349+
},
1350+
responses: []response{
1351+
// response for first serversInNginx GET servers
1352+
{
1353+
statusCode: http.StatusOK,
1354+
servers: []UpstreamServer{},
1355+
},
1356+
// response for addStreamServer POST server for server1
1357+
{
1358+
statusCode: http.StatusInternalServerError,
1359+
servers: []UpstreamServer{},
1360+
},
1361+
// response for addStreamServer POST server for server2
1362+
{
1363+
statusCode: http.StatusCreated,
1364+
servers: []UpstreamServer{},
1365+
},
1366+
},
1367+
expAdded: 1,
1368+
expErr: true,
10961369
},
10971370
}
10981371

1099-
handler := &fakeHandler{
1100-
func(w http.ResponseWriter, _ *http.Request) {
1101-
if len(responses) == 0 {
1102-
t.Fatal("ran out of responses")
1103-
}
1372+
for name, tc := range testcases {
1373+
t.Run(name, func(t *testing.T) {
1374+
t.Parallel()
11041375

1105-
re := responses[0]
1106-
responses = responses[1:]
1376+
var requests []*http.Request
1377+
handler := &fakeHandler{
1378+
func(w http.ResponseWriter, r *http.Request) {
1379+
requests = append(requests, r)
11071380

1108-
w.WriteHeader(re.statusCode)
1381+
if len(tc.responses) == 0 {
1382+
t.Fatal("ran out of responses")
1383+
}
1384+
if r.Method == http.MethodPost || r.Method == http.MethodPut {
1385+
contentType, ok := r.Header["Content-Type"]
1386+
if !ok {
1387+
t.Fatalf("expected request type %s to have a Content-Type header", r.Method)
1388+
}
1389+
if len(contentType) != 1 || contentType[0] != "application/json" {
1390+
t.Fatalf("expected request type %s to have a Content-Type header value of 'application/json'", r.Method)
1391+
}
1392+
}
11091393

1110-
resp, err := json.Marshal(re.servers)
1111-
if err != nil {
1112-
t.Fatal(err)
1113-
}
1114-
_, err = w.Write(resp)
1115-
if err != nil {
1116-
t.Fatal(err)
1117-
}
1118-
},
1119-
}
1394+
re := tc.responses[0]
1395+
tc.responses = tc.responses[1:]
11201396

1121-
server := httptest.NewServer(handler)
1122-
defer server.Close()
1397+
w.WriteHeader(re.statusCode)
11231398

1124-
client, err := NewNginxClient(server.URL, WithHTTPClient(&http.Client{}))
1125-
if err != nil {
1126-
t.Fatal(err)
1127-
}
1128-
1129-
streamServer1 := StreamUpstreamServer{Server: "127.0.0.1:2000"}
1130-
streamServer2 := StreamUpstreamServer{Server: "127.0.0.1:3000"}
1399+
resp, err := json.Marshal(re.servers)
1400+
if err != nil {
1401+
t.Fatal(err)
1402+
}
1403+
_, err = w.Write(resp)
1404+
if err != nil {
1405+
t.Fatal(err)
1406+
}
1407+
},
1408+
}
11311409

1132-
// we expect that we will get an error for the 500 error encountered when putting server1
1133-
// but we also expect that we get the second server added
1134-
added, _, _, err := client.UpdateStreamServers(context.TODO(), "fakeUpstream", []StreamUpstreamServer{
1135-
streamServer1,
1136-
streamServer2,
1137-
})
1138-
if err == nil {
1139-
t.Fatal("expected to receive an error for 500 response when adding first server")
1140-
}
1410+
server := httptest.NewServer(handler)
1411+
defer server.Close()
11411412

1142-
if len(added) != 1 {
1143-
t.Fatalf("expected to get one added server, instead got %d", len(added))
1144-
}
1413+
client, err := NewNginxClient(server.URL, WithHTTPClient(&http.Client{}))
1414+
if err != nil {
1415+
t.Fatal(err)
1416+
}
11451417

1146-
if !reflect.DeepEqual(streamServer2, added[0]) {
1147-
t.Errorf("expected: %v got: %v", streamServer2, added[0])
1418+
added, deleted, updated, err := client.UpdateStreamServers(context.Background(), "fakeUpstream", tc.reqServers)
1419+
if tc.expErr && err == nil {
1420+
t.Fatal("expected to receive an error")
1421+
}
1422+
if !tc.expErr && err != nil {
1423+
t.Fatalf("received an unexpected error: %v", err)
1424+
}
1425+
if len(added) != tc.expAdded {
1426+
t.Fatalf("expected to get %d added server(s), instead got %d", tc.expAdded, len(added))
1427+
}
1428+
if len(deleted) != tc.expDeleted {
1429+
t.Fatalf("expected to get %d deleted server(s), instead got %d", tc.expDeleted, len(deleted))
1430+
}
1431+
if len(updated) != tc.expUpdated {
1432+
t.Fatalf("expected to get %d updated server(s), instead got %d", tc.expUpdated, len(updated))
1433+
}
1434+
if len(tc.responses) != 0 {
1435+
t.Fatalf("did not use all expected responses, %d unused", len(tc.responses))
1436+
}
1437+
})
11481438
}
11491439
}
11501440

11511441
type response struct {
1152-
servers []UpstreamServer
1442+
servers interface{}
11531443
statusCode int
11541444
}
11551445

‎tests/client_test.go

+66-4
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,39 @@ func TestStreamClient(t *testing.T) {
6161
t.Errorf("Adding a duplicated server succeeded")
6262
}
6363

64-
// test deleting a stream server
64+
// test updating a stream server
65+
streamServers, err := c.GetStreamServers(ctx, streamUpstream)
66+
if err != nil {
67+
t.Errorf("Error getting stream servers: %v", err)
68+
}
69+
if len(streamServers) != 1 {
70+
t.Errorf("Expected 1 servers, got %v", streamServers)
71+
}
72+
73+
streamServers[0].SlowStart = "30s"
74+
err = c.UpdateStreamServer(ctx, streamUpstream, streamServers[0])
75+
if err != nil {
76+
t.Errorf("Error when updating a server: %v", err)
77+
}
6578

79+
streamServers, err = c.GetStreamServers(ctx, streamUpstream)
80+
if err != nil {
81+
t.Errorf("Error getting stream servers: %v", err)
82+
}
83+
if len(streamServers) != 1 {
84+
t.Errorf("Expected 1 servers, got %v", streamServers)
85+
}
86+
if streamServers[0].SlowStart != "30s" {
87+
t.Errorf("The server wasn't successfully updated: expected a 'SlowStart' of 30s, actual was %s", streamServers[0].SlowStart)
88+
}
89+
90+
streamServers[0].ID++
91+
err = c.UpdateStreamServer(ctx, streamUpstream, streamServers[0])
92+
if err == nil {
93+
t.Errorf("Updating a server without a matching server ID succeeded")
94+
}
95+
96+
// test deleting a stream server
6697
err = c.DeleteStreamServer(ctx, streamUpstream, streamServer.Server)
6798
if err != nil {
6899
t.Fatalf("Error when deleting a server: %v", err)
@@ -73,7 +104,7 @@ func TestStreamClient(t *testing.T) {
73104
t.Errorf("Deleting a nonexisting server succeeded")
74105
}
75106

76-
streamServers, err := c.GetStreamServers(ctx, streamUpstream)
107+
streamServers, err = c.GetStreamServers(ctx, streamUpstream)
77108
if err != nil {
78109
t.Errorf("Error getting stream servers: %v", err)
79110
}
@@ -340,8 +371,39 @@ func TestClient(t *testing.T) {
340371
t.Errorf("Adding a duplicated server succeeded")
341372
}
342373

343-
// test deleting a http server
374+
// test updating an http server
375+
servers, err := c.GetHTTPServers(ctx, upstream)
376+
if err != nil {
377+
t.Errorf("Error getting servers: %v", err)
378+
}
379+
if len(servers) != 1 {
380+
t.Errorf("Expected 1 servers, got %v", servers)
381+
}
382+
383+
servers[0].SlowStart = "30s"
384+
err = c.UpdateHTTPServer(ctx, upstream, servers[0])
385+
if err != nil {
386+
t.Errorf("Error when updating a server: %v", err)
387+
}
344388

389+
servers, err = c.GetHTTPServers(ctx, upstream)
390+
if err != nil {
391+
t.Errorf("Error getting servers: %v", err)
392+
}
393+
if len(servers) != 1 {
394+
t.Errorf("Expected 1 servers, got %v", servers)
395+
}
396+
if servers[0].SlowStart != "30s" {
397+
t.Errorf("The server wasn't successfully updated: expected a 'SlowStart' of 30s, actual was %s", servers[0].SlowStart)
398+
}
399+
400+
servers[0].ID++
401+
err = c.UpdateHTTPServer(ctx, upstream, servers[0])
402+
if err == nil {
403+
t.Errorf("Updating a server without a matching server ID succeeded")
404+
}
405+
406+
// test deleting a http server
345407
err = c.DeleteHTTPServer(ctx, upstream, server.Server)
346408
if err != nil {
347409
t.Fatalf("Error when deleting a server: %v", err)
@@ -381,7 +443,7 @@ func TestClient(t *testing.T) {
381443

382444
// test getting servers
383445

384-
servers, err := c.GetHTTPServers(ctx, upstream)
446+
servers, err = c.GetHTTPServers(ctx, upstream)
385447
if err != nil {
386448
t.Fatalf("Error when getting servers: %v", err)
387449
}

0 commit comments

Comments
 (0)
Please sign in to comment.