Skip to content

Commit

Permalink
Merge pull request #493 from alphagov/load-backends-from-env-vars
Browse files Browse the repository at this point in the history
Load backends from env vars
  • Loading branch information
theseanything authored Nov 4, 2024
2 parents 2e101c4 + 52cf5e8 commit 519a8bb
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 182 deletions.
66 changes: 49 additions & 17 deletions integration_tests/backend_helpers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package integration

import (
"net"
"net/http"
"net/http/httptest"
"strconv"
Expand All @@ -12,14 +13,39 @@ import (
"github.com/onsi/gomega/ghttp"
)

func startSimpleBackend(identifier string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var backends = map[string]string{
"backend-1": "127.0.0.1:6789",
"backend-2": "127.0.0.1:6790",
"outer": "127.0.0.1:6792",
"inner": "127.0.0.1:6793",
"innerer": "127.0.0.1:6794",
"root": "127.0.0.1:6795",
"other": "127.0.0.1:6796",
"fallthrough": "127.0.0.1:6797",
"down": "127.0.0.1:6798",
"slow-1": "127.0.0.1:6799",
"slow-2": "127.0.0.1:6800",
"backend": "127.0.0.1:6801",
"be": "127.0.0.1:6802",
"not-running": "127.0.0.1:6803",
"with-path": "127.0.0.1:6804",
}

func startSimpleBackend(identifier, host string) *httptest.Server {
l, err := net.Listen("tcp", host)
Expect(err).NotTo(HaveOccurred())

ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(identifier))
Expect(err).NotTo(HaveOccurred())
}))
ts.Listener.Close()
ts.Listener = l
ts.Start()
return ts
}

func startTarpitBackend(delays ...time.Duration) *httptest.Server {
func startTarpitBackend(host string, delays ...time.Duration) *httptest.Server {
responseDelay := 2 * time.Second
if len(delays) > 0 {
responseDelay = delays[0]
Expand All @@ -28,7 +54,11 @@ func startTarpitBackend(delays ...time.Duration) *httptest.Server {
if len(delays) > 1 {
bodyDelay = delays[1]
}
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

l, err := net.Listen("tcp", host)
Expect(err).NotTo(HaveOccurred())

ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body := "Tarpit\n"

if responseDelay > 0 {
Expand All @@ -44,24 +74,26 @@ func startTarpitBackend(delays ...time.Duration) *httptest.Server {
_, err := w.Write([]byte(body))
Expect(err).NotTo(HaveOccurred())
}))
ts.Listener.Close()
ts.Listener = l
ts.Start()
return ts
}

func startRecordingBackend() *ghttp.Server {
return startRecordingServer(false)
}

func startRecordingTLSBackend() *ghttp.Server {
return startRecordingServer(true)
}
func startRecordingBackend(tls bool, host string) *ghttp.Server {
l, err := net.Listen("tcp", host)
Expect(err).NotTo(HaveOccurred())

func startRecordingServer(tls bool) (server *ghttp.Server) {
ts := ghttp.NewUnstartedServer()
ts.HTTPTestServer.Listener.Close()
ts.HTTPTestServer.Listener = l
if tls {
server = ghttp.NewTLSServer()
ts.HTTPTestServer.StartTLS()
} else {
server = ghttp.NewServer()
ts.Start()
}

server.AllowUnhandledRequests = true
server.UnhandledRequestStatusCode = http.StatusOK
return server
ts.AllowUnhandledRequests = true
ts.UnhandledRequestStatusCode = http.StatusOK
return ts
}
9 changes: 8 additions & 1 deletion integration_tests/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ var _ = BeforeSuite(func() {
if err != nil {
Fail(err.Error())
}
err = startRouter(routerPort, apiPort, nil)

backendEnvVars := []string{}
for id, host := range backends {
envVar := "BACKEND_URL_" + id + "=http://" + host
backendEnvVars = append(backendEnvVars, envVar)
}

err = startRouter(routerPort, apiPort, backendEnvVars)
if err != nil {
Fail(err.Error())
}
Expand Down
24 changes: 9 additions & 15 deletions integration_tests/performance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ var _ = Describe("Performance", func() {
)

BeforeEach(func() {
backend1 = startSimpleBackend("backend 1")
backend2 = startSimpleBackend("backend 2")
addBackend("backend-1", backend1.URL)
addBackend("backend-2", backend2.URL)
backend1 = startSimpleBackend("backend 1", backends["backend-1"])
backend2 = startSimpleBackend("backend 2", backends["backend-2"])
addRoute("/one", NewBackendRoute("backend-1"))
addRoute("/two", NewBackendRoute("backend-2"))
reloadRoutes(apiPort)
Expand Down Expand Up @@ -58,10 +56,9 @@ var _ = Describe("Performance", func() {

Describe("with one slow backend hit separately", func() {
It("Router should not cause errors or much latency", func() {
slowBackend := startTarpitBackend(time.Second)
slowBackend := startTarpitBackend(backends["slow-1"], time.Second)
defer slowBackend.Close()
addBackend("backend-slow", slowBackend.URL)
addRoute("/slow", NewBackendRoute("backend-slow"))
addRoute("/slow", NewBackendRoute("slow-1"))
reloadRoutes(apiPort)

_, gen := generateLoad([]string{routerURL(routerPort, "/slow")}, 50)
Expand All @@ -73,8 +70,7 @@ var _ = Describe("Performance", func() {

Describe("with one downed backend hit separately", func() {
It("Router should not cause errors or much latency", func() {
addBackend("backend-down", "http://127.0.0.1:3162/")
addRoute("/down", NewBackendRoute("backend-down"))
addRoute("/down", NewBackendRoute("down"))
reloadRoutes(apiPort)

_, gen := generateLoad([]string{routerURL(routerPort, "/down")}, 50)
Expand All @@ -96,12 +92,10 @@ var _ = Describe("Performance", func() {
var backend2 *httptest.Server

BeforeEach(func() {
backend1 = startTarpitBackend(time.Second)
backend2 = startTarpitBackend(time.Second)
addBackend("backend-1", backend1.URL)
addBackend("backend-2", backend2.URL)
addRoute("/one", NewBackendRoute("backend-1"))
addRoute("/two", NewBackendRoute("backend-2"))
backend1 = startTarpitBackend(backends["slow-1"], time.Second)
backend2 = startTarpitBackend(backends["slow-2"], time.Second)
addRoute("/one", NewBackendRoute("slow-1"))
addRoute("/two", NewBackendRoute("slow-2"))
reloadRoutes(apiPort)
})
AfterEach(func() {
Expand Down
48 changes: 21 additions & 27 deletions integration_tests/proxy_function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ var _ = Describe("Functioning as a reverse proxy", func() {

Describe("connecting to the backend", func() {
It("should return a 502 if the connection to the backend is refused", func() {
addBackend("not-running", "http://127.0.0.1:3164/")
addRoute("/not-running", NewBackendRoute("not-running"))
reloadRoutes(apiPort)

Expand All @@ -31,21 +30,20 @@ var _ = Describe("Functioning as a reverse proxy", func() {

logDetails := lastRouterErrorLogEntry()
Expect(logDetails.Fields).To(Equal(map[string]interface{}{
"error": "dial tcp 127.0.0.1:3164: connect: connection refused",
"error": "dial tcp 127.0.0.1:6803: connect: connection refused",
"request": "GET /not-running HTTP/1.1",
"request_method": "GET",
"status": float64(502), // All numbers in JSON are floating point
"upstream_addr": "127.0.0.1:3164",
"upstream_addr": "127.0.0.1:6803",
}))
Expect(logDetails.Timestamp).To(BeTemporally("~", time.Now(), time.Second))
})

It("should log and return a 504 if the connection times out in the configured time", func() {
err := startRouter(3167, 3166, []string{"ROUTER_BACKEND_CONNECT_TIMEOUT=0.3s"})
err := startRouter(3167, 3166, []string{"ROUTER_BACKEND_CONNECT_TIMEOUT=0.3s", "BACKEND_URL_black-hole=http://240.0.0.0:1234/"})
Expect(err).NotTo(HaveOccurred())
defer stopRouter(3167)

addBackend("black-hole", "http://240.0.0.0:1234/")
addRoute("/should-time-out", NewBackendRoute("black-hole"))
reloadRoutes(3166)

Expand Down Expand Up @@ -74,14 +72,12 @@ var _ = Describe("Functioning as a reverse proxy", func() {
var tarpit1, tarpit2 *httptest.Server

BeforeEach(func() {
err := startRouter(3167, 3166, []string{"ROUTER_BACKEND_HEADER_TIMEOUT=0.3s"})
err := startRouter(3167, 3166, []string{"ROUTER_BACKEND_HEADER_TIMEOUT=0.3s", "BACKEND_URL_slow-1=http://127.0.0.1:6256/", "BACKEND_URL_slow-2=http://127.0.0.1:6253/"})
Expect(err).NotTo(HaveOccurred())
tarpit1 = startTarpitBackend(time.Second)
tarpit2 = startTarpitBackend(100*time.Millisecond, 500*time.Millisecond)
addBackend("tarpit1", tarpit1.URL)
addBackend("tarpit2", tarpit2.URL)
addRoute("/tarpit1", NewBackendRoute("tarpit1"))
addRoute("/tarpit2", NewBackendRoute("tarpit2"))
tarpit1 = startTarpitBackend("127.0.0.1:6256", time.Second)
tarpit2 = startTarpitBackend("127.0.0.1:6253", 100*time.Millisecond, 500*time.Millisecond)
addRoute("/tarpit1", NewBackendRoute("slow-1"))
addRoute("/tarpit2", NewBackendRoute("slow-2"))
reloadRoutes(3166)
})

Expand Down Expand Up @@ -118,8 +114,7 @@ var _ = Describe("Functioning as a reverse proxy", func() {

Describe("header handling", func() {
BeforeEach(func() {
recorder = startRecordingBackend()
addBackend("backend", recorder.URL())
recorder = startRecordingBackend(false, backends["backend"])
addRoute("/foo", NewBackendRoute("backend", "prefix"))
reloadRoutes(apiPort)
})
Expand Down Expand Up @@ -242,8 +237,7 @@ var _ = Describe("Functioning as a reverse proxy", func() {

Describe("request verb, path, query and body handling", func() {
BeforeEach(func() {
recorder = startRecordingBackend()
addBackend("backend", recorder.URL())
recorder = startRecordingBackend(false, backends["backend"])
addRoute("/foo", NewBackendRoute("backend", "prefix"))
reloadRoutes(apiPort)
})
Expand Down Expand Up @@ -298,18 +292,20 @@ var _ = Describe("Functioning as a reverse proxy", func() {

Describe("handling a backend with a non '/' path", func() {
BeforeEach(func() {
recorder = startRecordingBackend()
addBackend("backend", recorder.URL()+"/something")
addRoute("/foo/bar", NewBackendRoute("backend", "prefix"))
reloadRoutes(apiPort)
err := startRouter(3167, 3166, []string{"ROUTER_TLS_SKIP_VERIFY=1", "BACKEND_URL_with-path=http://127.0.0.1:6804/something"})
Expect(err).NotTo(HaveOccurred())
recorder = startRecordingBackend(false, backends["with-path"])
addRoute("/foo/bar", NewBackendRoute("with-path", "prefix"))
reloadRoutes(3166)
})

AfterEach(func() {
recorder.Close()
stopRouter(3167)
})

It("should merge the 2 paths", func() {
resp := routerRequest(routerPort, "/foo/bar")
resp := routerRequest(3167, "/foo/bar")
Expect(resp.StatusCode).To(Equal(200))

Expect(recorder.ReceivedRequests()).To(HaveLen(1))
Expand All @@ -318,7 +314,7 @@ var _ = Describe("Functioning as a reverse proxy", func() {
})

It("should preserve the request query string", func() {
resp := routerRequest(routerPort, "/foo/bar?baz=qux")
resp := routerRequest(3167, "/foo/bar?baz=qux")
Expect(resp.StatusCode).To(Equal(200))

Expect(recorder.ReceivedRequests()).To(HaveLen(1))
Expand All @@ -329,8 +325,7 @@ var _ = Describe("Functioning as a reverse proxy", func() {

Describe("handling HTTP/1.0 requests", func() {
BeforeEach(func() {
recorder = startRecordingBackend()
addBackend("backend", recorder.URL())
recorder = startRecordingBackend(false, backends["backend"])
addRoute("/foo", NewBackendRoute("backend", "prefix"))
reloadRoutes(apiPort)
})
Expand Down Expand Up @@ -362,10 +357,9 @@ var _ = Describe("Functioning as a reverse proxy", func() {

Describe("handling requests to a HTTPS backend", func() {
BeforeEach(func() {
err := startRouter(3167, 3166, []string{"ROUTER_TLS_SKIP_VERIFY=1"})
err := startRouter(3167, 3166, []string{"ROUTER_TLS_SKIP_VERIFY=1", "BACKEND_URL_backend=https://127.0.0.1:2486"})
Expect(err).NotTo(HaveOccurred())
recorder = startRecordingTLSBackend()
addBackend("backend", recorder.URL())
recorder = startRecordingBackend(true, "127.0.0.1:2486")
addRoute("/foo", NewBackendRoute("backend", "prefix"))
reloadRoutes(3166)
})
Expand Down
3 changes: 1 addition & 2 deletions integration_tests/redirect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,7 @@ var _ = Describe("Redirection", func() {
var recorder *ghttp.Server

BeforeEach(func() {
recorder = startRecordingBackend()
addBackend("be", recorder.URL())
recorder = startRecordingBackend(false, backends["be"])
addRoute("/guidance/keeping-a-pet-pig-or-micropig", NewBackendRoute("be", "exact"))
addRoute("/GUIDANCE/keeping-a-pet-pig-or-micropig", NewBackendRoute("be", "exact"))
reloadRoutes(apiPort)
Expand Down
6 changes: 0 additions & 6 deletions integration_tests/route_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"time"

"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"

// revive:disable:dot-imports
. "github.com/onsi/ginkgo/v2"
Expand Down Expand Up @@ -91,11 +90,6 @@ func initRouteHelper() error {
return nil
}

func addBackend(id, url string) {
err := routerDB.C("backends").Insert(bson.M{"backend_id": id, "backend_url": url})
Expect(err).NotTo(HaveOccurred())
}

func addRoute(path string, route Route) {
route.IncomingPath = path

Expand Down
38 changes: 2 additions & 36 deletions integration_tests/route_loading_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package integration

import (
"fmt"
"net/http/httptest"

. "github.com/onsi/ginkgo/v2"
Expand All @@ -12,14 +11,11 @@ var _ = Describe("loading routes from the db", func() {
var (
backend1 *httptest.Server
backend2 *httptest.Server
backend3 *httptest.Server
)

BeforeEach(func() {
backend1 = startSimpleBackend("backend 1")
backend2 = startSimpleBackend("backend 2")
addBackend("backend-1", backend1.URL)
addBackend("backend-2", backend2.URL)
backend1 = startSimpleBackend("backend 1", backends["backend-1"])
backend2 = startSimpleBackend("backend 2", backends["backend-2"])
})
AfterEach(func() {
backend1.Close()
Expand Down Expand Up @@ -73,34 +69,4 @@ var _ = Describe("loading routes from the db", func() {
Expect(readBody(resp)).To(Equal("backend 1"))
})
})

Context("a backend an env var overriding the backend_url", func() {
BeforeEach(func() {
// This tests the behaviour of backend.ParseURL overriding the backend_url
// provided in the DB with the value of an env var
blackHole := "240.0.0.0/foo"
backend3 = startSimpleBackend("backend 3")
addBackend("backend-3", blackHole)

stopRouter(routerPort)
err := startRouter(routerPort, apiPort, []string{fmt.Sprintf("BACKEND_URL_backend-3=%s", backend3.URL)})
Expect(err).NotTo(HaveOccurred())

addRoute("/oof", NewBackendRoute("backend-3"))
reloadRoutes(apiPort)
})

AfterEach(func() {
stopRouter(routerPort)
err := startRouter(routerPort, apiPort, nil)
Expect(err).NotTo(HaveOccurred())
backend3.Close()
})

It("should send requests to the backend_url provided in the env var", func() {
resp := routerRequest(routerPort, "/oof")
Expect(resp.StatusCode).To(Equal(200))
Expect(readBody(resp)).To(Equal("backend 3"))
})
})
})
Loading

0 comments on commit 519a8bb

Please sign in to comment.