Add Global Rate Limiting support

This commit is contained in:
Elvin Efendi 2020-12-24 11:39:12 -05:00
parent 14345ebcfe
commit e0dece48f7
21 changed files with 1179 additions and 38 deletions

View file

@ -0,0 +1,83 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package annotations
import (
"fmt"
"net/http"
"strings"
"github.com/onsi/ginkgo"
"github.com/stretchr/testify/assert"
"k8s.io/ingress-nginx/test/e2e/framework"
)
var _ = framework.DescribeAnnotation("annotation-global-rate-limit", func() {
f := framework.NewDefaultFramework("global-rate-limit")
host := "global-rate-limit-annotation"
ginkgo.BeforeEach(func() {
f.NewEchoDeployment()
})
ginkgo.It("generates correct configuration", func() {
annotations := make(map[string]string)
annotations["nginx.ingress.kubernetes.io/global-rate-limit"] = "5"
annotations["nginx.ingress.kubernetes.io/global-rate-limit-window"] = "2m"
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
ing = f.EnsureIngress(ing)
namespace := strings.Replace(string(ing.UID), "-", "", -1)
serverConfig := ""
f.WaitForNginxServer(host, func(server string) bool {
serverConfig = server
return true
})
assert.Contains(ginkgo.GinkgoT(), serverConfig,
fmt.Sprintf(`global_throttle = { namespace = "%v", `+
`limit = 5, window_size = 120, key = { { nil, nil, "remote_addr", nil, }, }, `+
`ignored_cidrs = { } }`,
namespace))
f.HTTPTestClient().GET("/").WithHeader("Host", host).Expect().Status(http.StatusOK)
ginkgo.By("regenerating the correct configuration after update")
annotations["nginx.ingress.kubernetes.io/global-rate-limit-key"] = "${remote_addr}${http_x_api_client}"
annotations["nginx.ingress.kubernetes.io/global-rate-limit-ignored-cidrs"] = "192.168.1.1, 234.234.234.0/24"
ing.SetAnnotations(annotations)
f.WaitForReload(func() {
ing = f.UpdateIngress(ing)
})
serverConfig = ""
f.WaitForNginxServer(host, func(server string) bool {
serverConfig = server
return true
})
assert.Contains(ginkgo.GinkgoT(), serverConfig,
fmt.Sprintf(`global_throttle = { namespace = "%v", `+
`limit = 5, window_size = 120, `+
`key = { { nil, "remote_addr", nil, nil, }, { nil, "http_x_api_client", nil, nil, }, }, `+
`ignored_cidrs = { "192.168.1.1", "234.234.234.0/24", } }`,
namespace))
f.HTTPTestClient().GET("/").WithHeader("Host", host).Expect().Status(http.StatusOK)
})
})

View file

@ -215,7 +215,8 @@ func (f *Framework) updateIngressNGINXPod() error {
return err
}
// WaitForNginxServer waits until the nginx configuration contains a particular server section
// WaitForNginxServer waits until the nginx configuration contains a particular server section.
// `cfg` passed to matcher is normalized by replacing all tabs and spaces with single space.
func (f *Framework) WaitForNginxServer(name string, matcher func(cfg string) bool) {
err := wait.Poll(Poll, DefaultTimeout, f.matchNginxConditions(name, matcher))
assert.Nil(ginkgo.GinkgoT(), err, "waiting for nginx server condition/s")
@ -223,6 +224,7 @@ func (f *Framework) WaitForNginxServer(name string, matcher func(cfg string) boo
}
// WaitForNginxConfiguration waits until the nginx configuration contains a particular configuration
// `cfg` passed to matcher is normalized by replacing all tabs and spaces with single space.
func (f *Framework) WaitForNginxConfiguration(matcher func(cfg string) bool) {
err := wait.Poll(Poll, DefaultTimeout, f.matchNginxConditions("", matcher))
assert.Nil(ginkgo.GinkgoT(), err, "waiting for nginx server condition/s")
@ -325,7 +327,7 @@ func (f *Framework) SetNginxConfigMapData(cmData map[string]string) {
assert.Nil(ginkgo.GinkgoT(), err, "updating configuration configmap")
}
f.waitForReload(fn)
f.WaitForReload(fn)
}
// CreateConfigMap creates a new configmap in the current namespace
@ -356,10 +358,12 @@ func (f *Framework) UpdateNginxConfigMapData(key string, value string) {
assert.Nil(ginkgo.GinkgoT(), err, "updating configuration configmap")
}
f.waitForReload(fn)
f.WaitForReload(fn)
}
func (f *Framework) waitForReload(fn func()) {
// WaitForReload calls the passed function and
// asser it has caused at least 1 reload.
func (f *Framework) WaitForReload(fn func()) {
initialReloadCount := getReloadCount(f.pod, f.Namespace, f.KubeClientSet)
fn()

View file

@ -76,7 +76,7 @@ func (f *Framework) EnsureIngress(ingress *networking.Ingress) *networking.Ingre
assert.Nil(ginkgo.GinkgoT(), err, "creating ingress")
}
f.waitForReload(fn)
f.WaitForReload(fn)
ing := f.GetIngress(f.Namespace, ingress.Name)
if ing.Annotations == nil {

View file

@ -0,0 +1,96 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package settings
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/onsi/ginkgo"
"github.com/stretchr/testify/assert"
"k8s.io/ingress-nginx/test/e2e/framework"
)
var _ = framework.DescribeSetting("settings-global-rate-limit", func() {
f := framework.NewDefaultFramework("global-rate-limit")
host := "global-rate-limit"
ginkgo.BeforeEach(func() {
f.NewEchoDeployment()
})
ginkgo.It("generates correct NGINX configuration", func() {
annotations := make(map[string]string)
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
f.EnsureIngress(ing)
ginkgo.By("generating correct defaults")
ngxCfg := ""
f.WaitForNginxConfiguration(func(cfg string) bool {
if strings.Contains(cfg, "global_throttle") {
ngxCfg = cfg
return true
}
return false
})
assert.Contains(ginkgo.GinkgoT(), ngxCfg, fmt.Sprintf(`global_throttle = { `+
`memcached = { host = "%v", port = %d, connect_timeout = %d, max_idle_timeout = %d, `+
`pool_size = %d, }, status_code = %d, }`,
"", 11211, 50, 10000, 50, 429))
f.HTTPTestClient().GET("/").WithHeader("Host", host).Expect().Status(http.StatusOK)
ginkgo.By("applying customizations")
memcachedHost := "memc.default.svc.cluster.local"
memcachedPort := 11211
memcachedConnectTimeout := 100
memcachedMaxIdleTimeout := 5000
memcachedPoolSize := 100
statusCode := 503
f.SetNginxConfigMapData(map[string]string{
"global-rate-limit-memcached-host": memcachedHost,
"global-rate-limit-memcached-port": strconv.Itoa(memcachedPort),
"global-rate-limit-memcached-connect-timeout": strconv.Itoa(memcachedConnectTimeout),
"global-rate-limit-memcached-max-idle-timeout": strconv.Itoa(memcachedMaxIdleTimeout),
"global-rate-limit-memcached-pool-size": strconv.Itoa(memcachedPoolSize),
"global-rate-limit-status-code": strconv.Itoa(statusCode),
})
ngxCfg = ""
f.WaitForNginxConfiguration(func(cfg string) bool {
if strings.Contains(cfg, "global_throttle") {
ngxCfg = cfg
return true
}
return false
})
assert.Contains(ginkgo.GinkgoT(), ngxCfg, fmt.Sprintf(`global_throttle = { `+
`memcached = { host = "%v", port = %d, connect_timeout = %d, max_idle_timeout = %d, `+
`pool_size = %d, }, status_code = %d, }`,
memcachedHost, memcachedPort, memcachedConnectTimeout, memcachedMaxIdleTimeout,
memcachedPoolSize, statusCode))
f.HTTPTestClient().GET("/").WithHeader("Host", host).Expect().Status(http.StatusOK)
})
})