Add read-only, rate limited endpoint

pull/6/head
Daniel Smith 2014-10-20 15:23:28 -07:00
parent 6c434e6646
commit 9356ed7fe7
4 changed files with 102 additions and 3 deletions

View File

@ -49,6 +49,7 @@ import (
var (
port = flag.Uint("port", 8080, "The port to listen on. Default 8080")
address = util.IP(net.ParseIP("127.0.0.1"))
readOnlyPort = flag.Uint("read_only_port", 7080, "The port from which to serve read-only resources. If 0, don't serve on a read-only address.")
apiPrefix = flag.String("api_prefix", "/api", "The prefix for API requests on the server. Default '/api'.")
storageVersion = flag.String("storage_version", "", "The version to store resources with. Defaults to server preferred")
cloudProvider = flag.String("cloud_provider", "", "The provider for cloud services. Empty string for no provider.")
@ -230,11 +231,25 @@ func main() {
handler = handlers.NewRequestAuthenticator(userContexts, bearertoken.New(auth), handlers.Unauthorized, handler)
}
handler = apiserver.RecoverPanics(handler)
if *readOnlyPort != 0 {
// Allow 1 read-only request per second, allow up to 20 in a burst before enforcing.
rl := util.NewTokenBucketRateLimiter(1.0, 20)
readOnlyServer := &http.Server{
Addr: net.JoinHostPort(address.String(), strconv.Itoa(int(*readOnlyPort))),
Handler: apiserver.RecoverPanics(apiserver.ReadOnly(apiserver.RateLimit(rl, handler))),
ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute,
MaxHeaderBytes: 1 << 20,
}
go func() {
defer util.HandleCrash()
glog.Fatal(readOnlyServer.ListenAndServe())
}()
}
s := &http.Server{
Addr: net.JoinHostPort(address.String(), strconv.Itoa(int(*port))),
Handler: handler,
Handler: apiserver.RecoverPanics(handler),
ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute,
MaxHeaderBytes: 1 << 20,

View File

@ -200,7 +200,7 @@ func TestNotFound(t *testing.T) {
for k, v := range cases {
request, err := http.NewRequest(v.Method, server.URL+v.Path, nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
t.Fatalf("unexpected error: %v", err)
}
response, err := client.Do(request)

View File

@ -24,9 +24,34 @@ import (
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/golang/glog"
)
// ReadOnly passes all GET requests on to handler, and returns an error on all other requests.
func ReadOnly(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if req.Method == "GET" {
handler.ServeHTTP(w, req)
return
}
w.WriteHeader(http.StatusForbidden)
fmt.Fprintf(w, "This is a read-only endpoint.")
})
}
// RateLimit uses rl to rate limit accepting requests to 'handler'.
func RateLimit(rl util.RateLimiter, handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if rl.CanAccept() {
handler.ServeHTTP(w, req)
return
}
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprintf(w, "Rate limit exceeded.")
})
}
// RecoverPanics wraps an http Handler to recover and log panics.
func RecoverPanics(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

View File

@ -0,0 +1,59 @@
/*
Copyright 2014 Google Inc. All rights reserved.
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 apiserver
import (
"net/http"
"net/http/httptest"
"testing"
)
type fakeRL bool
func (fakeRL) Stop() {}
func (f fakeRL) CanAccept() bool { return bool(f) }
func TestRateLimit(t *testing.T) {
for _, allow := range []bool{true, false} {
rl := fakeRL(allow)
server := httptest.NewServer(RateLimit(rl, http.HandlerFunc(
func(w http.ResponseWriter, req *http.Request) {
if !allow {
t.Errorf("Unexpected call")
}
},
)))
http.Get(server.URL)
}
}
func TestReadOnly(t *testing.T) {
server := httptest.NewServer(ReadOnly(http.HandlerFunc(
func(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" {
t.Errorf("Unexpected call: %v", req.Method)
}
},
)))
for _, verb := range []string{"GET", "POST", "PUT", "DELETE", "CREATE"} {
req, err := http.NewRequest(verb, server.URL, nil)
if err != nil {
t.Fatalf("Couldn't make request: %v", err)
}
http.DefaultClient.Do(req)
}
}