Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor https server into its own component #643

Merged
merged 1 commit into from
Mar 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"agones.dev/agones/pkg/gameservers"
"agones.dev/agones/pkg/gameserversets"
"agones.dev/agones/pkg/metrics"
"agones.dev/agones/pkg/util/https"
"agones.dev/agones/pkg/util/runtime"
"agones.dev/agones/pkg/util/signals"
"agones.dev/agones/pkg/util/webhooks"
Expand Down Expand Up @@ -136,7 +137,9 @@ func main() {
logger.WithError(err).Fatal("Could not create the agones api clientset")
}

wh := webhooks.NewWebHook(ctlConf.CertFile, ctlConf.KeyFile)
// https server and the items that share the Mux for routing
httpsServer := https.NewServer(ctlConf.CertFile, ctlConf.KeyFile)
wh := webhooks.NewWebHook(httpsServer.Mux)
agonesInformerFactory := externalversions.NewSharedInformerFactory(agonesClient, defaultResync)
kubeInformerFactory := informers.NewSharedInformerFactory(kubeClient, defaultResync)

Expand Down Expand Up @@ -196,7 +199,7 @@ func main() {
kubeClient, extClient, agonesClient, agonesInformerFactory)

rs = append(rs,
wh, gsController, gsSetController, fleetController, faController, fasController, gasController, server)
httpsServer, gsController, gsSetController, fleetController, faController, fasController, gasController, server)

stop := signals.NewStopChannel()

Expand Down
3 changes: 2 additions & 1 deletion pkg/fleetallocation/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package fleetallocation
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"sync"
"testing"
Expand Down Expand Up @@ -384,7 +385,7 @@ func defaultFixtures(gsLen int) (*v1alpha1.Fleet, *v1alpha1.GameServerSet, []v1a
// newFakeController returns a controller, backed by the fake Clientset
func newFakeController() (*Controller, agtesting.Mocks) {
m := agtesting.NewMocks()
wh := webhooks.NewWebHook("", "")
wh := webhooks.NewWebHook(http.NewServeMux())
c := NewController(wh, &sync.Mutex{}, m.KubeClient, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory)
c.recorder = m.FakeRecorder
return c, m
Expand Down
3 changes: 2 additions & 1 deletion pkg/fleetautoscalers/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package fleetautoscalers
import (
"encoding/json"
"fmt"
"net/http"
"testing"

"agones.dev/agones/pkg/apis/stable/v1alpha1"
Expand Down Expand Up @@ -503,7 +504,7 @@ func defaultWebhookFixtures() (*v1alpha1.FleetAutoscaler, *v1alpha1.Fleet) {
// newFakeController returns a controller, backed by the fake Clientset
func newFakeController() (*Controller, agtesting.Mocks) {
m := agtesting.NewMocks()
wh := webhooks.NewWebHook("", "")
wh := webhooks.NewWebHook(http.NewServeMux())
c := NewController(wh, healthcheck.NewHandler(), m.KubeClient, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory)
c.recorder = m.FakeRecorder
return c, m
Expand Down
3 changes: 2 additions & 1 deletion pkg/fleets/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package fleets

import (
"encoding/json"
"net/http"
"testing"
"time"

Expand Down Expand Up @@ -778,7 +779,7 @@ func TestControllerRollingUpdateDeployment(t *testing.T) {
// newFakeController returns a controller, backed by the fake Clientset
func newFakeController() (*Controller, agtesting.Mocks) {
m := agtesting.NewMocks()
wh := webhooks.NewWebHook("", "")
wh := webhooks.NewWebHook(http.NewServeMux())
c := NewController(wh, healthcheck.NewHandler(), m.KubeClient, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory)
c.recorder = m.FakeRecorder
return c, m
Expand Down
3 changes: 2 additions & 1 deletion pkg/gameserverallocations/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package gameserverallocations
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"sync"
"testing"
Expand Down Expand Up @@ -806,7 +807,7 @@ func defaultFixtures(gsLen int) (*v1alpha1.Fleet, *v1alpha1.GameServerSet, []v1a
// newFakeController returns a controller, backed by the fake Clientset
func newFakeController() (*Controller, agtesting.Mocks) {
m := agtesting.NewMocks()
wh := webhooks.NewWebHook("", "")
wh := webhooks.NewWebHook(http.NewServeMux())
c := NewController(wh, healthcheck.NewHandler(), m.KubeClient, m.KubeInformerFactory, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory, 1)
c.recorder = m.FakeRecorder
return c, m
Expand Down
3 changes: 2 additions & 1 deletion pkg/gameservers/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"testing"

"agones.dev/agones/pkg/apis/stable"
Expand Down Expand Up @@ -1185,7 +1186,7 @@ func testWithNonZeroDeletionTimestamp(t *testing.T, f func(*Controller, *v1alpha
// newFakeController returns a controller, backed by the fake Clientset
func newFakeController() (*Controller, agtesting.Mocks) {
m := agtesting.NewMocks()
wh := webhooks.NewWebHook("", "")
wh := webhooks.NewWebHook(http.NewServeMux())
c := NewController(wh, healthcheck.NewHandler(),
10, 20, "sidecar:dev", false,
resource.MustParse("0.05"), resource.MustParse("0.1"), m.KubeClient, m.KubeInformerFactory, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory)
Expand Down
3 changes: 2 additions & 1 deletion pkg/gameserversets/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package gameserversets

import (
"encoding/json"
"net/http"
"strconv"
"testing"
"time"
Expand Down Expand Up @@ -571,7 +572,7 @@ func createGameServers(gsSet *v1alpha1.GameServerSet, size int) []v1alpha1.GameS
// newFakeController returns a controller, backed by the fake Clientset
func newFakeController() (*Controller, agtesting.Mocks) {
m := agtesting.NewMocks()
wh := webhooks.NewWebHook("", "")
wh := webhooks.NewWebHook(http.NewServeMux())
c := NewController(wh, healthcheck.NewHandler(), m.KubeClient, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory)
c.recorder = m.FakeRecorder
return c, m
Expand Down
68 changes: 68 additions & 0 deletions pkg/util/https/https.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2019 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 https

import (
"io/ioutil"
"net/http"

"agones.dev/agones/pkg/util/runtime"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

// ErrorHandlerFunc is a http handler that can return an error
// for standard logging and a 500 response
type ErrorHandlerFunc func(http.ResponseWriter, *http.Request) error

// FourZeroFour is the standard 404 handler.
func FourZeroFour(logger *logrus.Entry, w http.ResponseWriter, r *http.Request) {
f := ErrorHTTPHandler(logger, func(writer http.ResponseWriter, request *http.Request) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return errors.Wrap(err, "error in default handler")
}
defer r.Body.Close() // nolint: errcheck

LogRequest(logger, r).WithField("body", string(body)).Warn("404")
http.NotFound(w, r)

return nil
})

f(w, r)
}

// ErrorHTTPHandler is a conversion function that sets up a http.StatusInternalServerError
// if an error is returned
func ErrorHTTPHandler(logger *logrus.Entry, f ErrorHandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
err := f(w, r)
if err != nil {
runtime.HandleError(LogRequest(logger, r), err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}

// LogRequest logs all the JSON parsable fields in a request
// as otherwise, the request is not marshable
func LogRequest(logger *logrus.Entry, r *http.Request) *logrus.Entry {
return logger.WithField("method", r.Method).
WithField("url", r.URL).
WithField("host", r.Host).
WithField("headers", r.Header).
WithField("requestURI", r.RequestURI)
}
39 changes: 39 additions & 0 deletions pkg/util/https/https_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2019 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 https

import (
"bytes"
"net/http"
"net/http/httptest"
"testing"

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestFourZeroFour(t *testing.T) {
b := bytes.NewBuffer(nil)
r, err := http.NewRequest(http.MethodGet, "/", b)
assert.NoError(t, err)
w := httptest.NewRecorder()

l := logrus.WithField("source", "test")

FourZeroFour(l, w, r)

resp := w.Result()
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
}
91 changes: 91 additions & 0 deletions pkg/util/https/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2019 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 https

import (
"net/http"

"agones.dev/agones/pkg/util/runtime"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

// tls is a http server interface to enable easier testing
type tls interface {
Close() error
ListenAndServeTLS(certFile, keyFile string) error
}

// Server is a HTTPs server that conforms to the runner interface
// we use in /cmd/controller, and has a public Mux that can be updated
// has a default 404 handler, to make discovery of k8s services a bit easier.
type Server struct {
logger *logrus.Entry
Mux *http.ServeMux
tls tls
certFile string
keyFile string
}

// NewServer returns a Server instance.
func NewServer(certFile, keyFile string) *Server {
mux := http.NewServeMux()
tls := &http.Server{
Addr: ":8081",
Handler: mux,
}

wh := &Server{
Mux: mux,
tls: tls,
certFile: certFile,
keyFile: keyFile,
}
wh.Mux.HandleFunc("/", wh.defaultHandler)
wh.logger = runtime.NewLoggerWithType(wh)

return wh
}

// Run runs the webhook server, starting a https listener.
// Will close the http server on stop channel close.
func (s *Server) Run(_ int, stop <-chan struct{}) error {
go func() {
<-stop
s.tls.Close() // nolint: errcheck,gosec
}()

s.logger.WithField("server", s).Infof("https server started")

err := s.tls.ListenAndServeTLS(s.certFile, s.keyFile)
if err == http.ErrServerClosed {
s.logger.WithError(err).Info("https server closed")
return nil
}

return errors.Wrap(err, "Could not listen on :8081")
}

// defaultHandler Handles all the HTTP requests
// useful for debugging requests
func (s *Server) defaultHandler(w http.ResponseWriter, r *http.Request) {
// "/" is the default health check used by APIServers
if r.URL.Path == "/" {
w.WriteHeader(http.StatusOK)
return
}

FourZeroFour(s.logger, w, r)
}
61 changes: 61 additions & 0 deletions pkg/util/https/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2019 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 https

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

type testServer struct {
server *httptest.Server
}

func (ts *testServer) Close() error {
ts.server.Close()
return nil
}

// ListenAndServeTLS(certFile, keyFile string) error
func (ts *testServer) ListenAndServeTLS(certFile, keyFile string) error {
ts.server.StartTLS()
return nil
}

func TestServerRun(t *testing.T) {
t.Parallel()

s := NewServer("", "")
ts := &testServer{server: httptest.NewUnstartedServer(s.Mux)}
s.tls = ts

stop := make(chan struct{})
defer close(stop)

err := s.Run(0, stop)
assert.Nil(t, err)

client := ts.server.Client()
resp, err := client.Get(ts.server.URL + "/test")
assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, resp.StatusCode)

resp, err = client.Get(ts.server.URL + "/")
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
Loading