blob: 361a6f0dca3ba877b88d9e3d221b2ab3ed729fde [file] [log] [blame]
package goscaffold
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"sync/atomic"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var insecureClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
var _ = Describe("Scaffold Tests", func() {
It("Validate framework", func() {
s := CreateHTTPScaffold()
stopChan := make(chan error)
err := s.Open()
Expect(err).Should(Succeed())
go func() {
fmt.Fprintf(GinkgoWriter, "Gonna listen on %s\n", s.InsecureAddress())
stopErr := s.Listen(&testHandler{})
fmt.Fprintf(GinkgoWriter, "Done listening\n")
stopChan <- stopErr
}()
Eventually(func() bool {
return testGet(s, "")
}, 5*time.Second).Should(BeTrue())
resp, err := http.Get(fmt.Sprintf("http://%s", s.InsecureAddress()))
Expect(err).Should(Succeed())
resp.Body.Close()
Expect(resp.StatusCode).Should(Equal(200))
shutdownErr := errors.New("Validate")
s.Shutdown(shutdownErr)
Eventually(stopChan).Should(Receive(Equal(shutdownErr)))
})
It("Separate management port", func() {
s := CreateHTTPScaffold()
s.SetManagementPort(0)
stopChan := make(chan error)
err := s.Open()
Expect(err).Should(Succeed())
go func() {
fmt.Fprintf(GinkgoWriter, "Gonna listen on %s and %s\n",
s.InsecureAddress(), s.ManagementAddress())
stopErr := s.Listen(&testHandler{})
fmt.Fprintf(GinkgoWriter, "Done listening\n")
stopChan <- stopErr
}()
// Just make sure that it's up
Eventually(func() bool {
return testGet(s, "")
}, 5*time.Second).Should(BeTrue())
resp, err := http.Get(fmt.Sprintf("http://%s", s.InsecureAddress()))
Expect(err).Should(Succeed())
resp.Body.Close()
Expect(resp.StatusCode).Should(Equal(200))
resp, err = http.Get(fmt.Sprintf("http://%s", s.ManagementAddress()))
Expect(err).Should(Succeed())
resp.Body.Close()
Expect(resp.StatusCode).Should(Equal(404))
shutdownErr := errors.New("Validate")
s.Shutdown(shutdownErr)
Eventually(stopChan).Should(Receive(Equal(shutdownErr)))
})
It("Shutdown", func() {
s := CreateHTTPScaffold()
s.SetHealthPath("/health")
s.SetReadyPath("/ready")
stopChan := make(chan error)
err := s.Open()
Expect(err).Should(Succeed())
go func() {
stopErr := s.Listen(&testHandler{})
stopChan <- stopErr
}()
go func() {
code, _ := getText(fmt.Sprintf("http://%s?delay=1s", s.InsecureAddress()))
Expect(code).Should(Equal(200))
}()
// Just make sure server is listening
Eventually(func() bool {
return testGet(s, "")
}, 5*time.Second).Should(BeTrue())
// Ensure that we are healthy and ready
code, _ := getText(fmt.Sprintf("http://%s/health", s.InsecureAddress()))
Expect(code).Should(Equal(200))
code, _ = getText(fmt.Sprintf("http://%s/ready", s.InsecureAddress()))
Expect(code).Should(Equal(200))
// Previous call prevents server from exiting
Consistently(stopChan, 250*time.Millisecond).ShouldNot(Receive())
// Tell the server to try and exit
stopErr := errors.New("Stop")
s.Shutdown(stopErr)
// Should take one second -- in the meantime, calls should fail with 503,
// health should be good, but ready should be bad
code, _ = getText(fmt.Sprintf("http://%s", s.InsecureAddress()))
Expect(code).Should(Equal(503))
code, _ = getText(fmt.Sprintf("http://%s/ready", s.InsecureAddress()))
Expect(code).Should(Equal(503))
code, _ = getText(fmt.Sprintf("http://%s/health", s.InsecureAddress()))
Expect(code).Should(Equal(200))
// Do a bunch more stops because we are funny that way.
// We just want to make sure that we don't hang if we stop a FEW times.
for i := 0; i < 25; i++ {
s.Shutdown(stopErr)
}
// But in less than two seconds, server should be down
Eventually(stopChan, 2*time.Second).Should(Receive(Equal(stopErr)))
// Calls should now fail
Eventually(func() bool {
return testGet(s, "")
}, time.Second).Should(BeFalse())
})
It("Markdown", func() {
var markedDown int32
s := CreateHTTPScaffold()
s.SetHealthPath("/health")
s.SetReadyPath("/ready")
s.SetMarkdown("POST", "/markdown", func() {
atomic.StoreInt32(&markedDown, 1)
})
stopChan := make(chan error)
err := s.Open()
Expect(err).Should(Succeed())
go func() {
listenErr := s.Listen(&testHandler{})
stopChan <- listenErr
}()
// Just make sure server is listening
Eventually(func() bool {
return testGet(s, "")
}, 5*time.Second).Should(BeTrue())
// Ensure that we are healthy and ready
code, _ := getText(fmt.Sprintf("http://%s/health", s.InsecureAddress()))
Expect(code).Should(Equal(200))
code, _ = getText(fmt.Sprintf("http://%s/ready", s.InsecureAddress()))
Expect(code).Should(Equal(200))
// Mark the server down, but don't stop it
resp, err := http.Post(fmt.Sprintf("http://%s/markdown", s.InsecureAddress()),
"text/plain", strings.NewReader("Goodbye!"))
Expect(err).Should(Succeed())
resp.Body.Close()
Expect(resp.StatusCode).Should(Equal(200))
// Server should immediately be marked down, not ready, but healthy
Expect(atomic.LoadInt32(&markedDown)).Should(BeEquivalentTo(1))
code, _ = getText(fmt.Sprintf("http://%s", s.InsecureAddress()))
Expect(code).Should(Equal(503))
code, _ = getText(fmt.Sprintf("http://%s/ready", s.InsecureAddress()))
Expect(code).Should(Equal(503))
code, _ = getText(fmt.Sprintf("http://%s/health", s.InsecureAddress()))
Expect(code).Should(Equal(200))
// Server should not have stopped yet
Consistently(stopChan).ShouldNot(Receive())
stopErr := errors.New("Test stop")
s.Shutdown(stopErr)
Eventually(stopChan).Should(Receive(Equal(stopErr)))
})
It("Health Check Functions", func() {
status := int32(OK)
var healthErr = &atomic.Value{}
statusFunc := func() (HealthStatus, error) {
stat := HealthStatus(atomic.LoadInt32(&status))
av := healthErr.Load()
if av != nil {
errPtr := av.(*error)
return stat, *errPtr
}
return stat, nil
}
s := CreateHTTPScaffold()
s.SetManagementPort(0)
s.SetHealthPath("/health")
s.SetReadyPath("/ready")
s.SetHealthChecker(statusFunc)
stopChan := make(chan error)
err := s.Open()
Expect(err).Should(Succeed())
go func() {
fmt.Fprintf(GinkgoWriter, "Gonna listen on %s and %s\n",
s.InsecureAddress(), s.ManagementAddress())
stopErr := s.Listen(&testHandler{})
fmt.Fprintf(GinkgoWriter, "Done listening\n")
stopChan <- stopErr
}()
// Just make sure that it's up
Eventually(func() bool {
return testGet(s, "")
}, 5*time.Second).Should(BeTrue())
// Health should be good
code, _ := getText(fmt.Sprintf("http://%s/health", s.ManagementAddress()))
Expect(code).Should(Equal(200))
code, _ = getText(fmt.Sprintf("http://%s/ready", s.ManagementAddress()))
Expect(code).Should(Equal(200))
// Mark down to "unhealthy" state. Should be bad.
atomic.StoreInt32(&status, int32(Failed))
code, bod := getText(fmt.Sprintf("http://%s/health", s.ManagementAddress()))
Expect(code).Should(Equal(503))
Expect(bod).Should(Equal("Failed"))
code, _ = getText(fmt.Sprintf("http://%s/ready", s.ManagementAddress()))
Expect(code).Should(Equal(503))
// Change to merely "not ready" state. Should be healthy but not ready.
atomic.StoreInt32(&status, int32(NotReady))
code, _ = getText(fmt.Sprintf("http://%s/health", s.ManagementAddress()))
Expect(code).Should(Equal(200))
code, bod = getText(fmt.Sprintf("http://%s/ready", s.ManagementAddress()))
Expect(code).Should(Equal(503))
Expect(bod).Should(Equal("NotReady"))
// Customize the error message.
customErr := errors.New("Custom")
healthErr.Store(&customErr)
code, bod = getText(fmt.Sprintf("http://%s/ready", s.ManagementAddress()))
Expect(code).Should(Equal(503))
Expect(bod).Should(Equal("Custom"))
// Check it in JSON
code, js := getJSON(fmt.Sprintf("http://%s/ready", s.ManagementAddress()))
Expect(code).Should(Equal(503))
Expect(js["status"]).Should(Equal("NotReady"))
Expect(js["reason"]).Should(Equal("Custom"))
// Mark back up. Should be all good
atomic.StoreInt32(&status, int32(OK))
code, _ = getText(fmt.Sprintf("http://%s/health", s.ManagementAddress()))
Expect(code).Should(Equal(200))
code, _ = getText(fmt.Sprintf("http://%s/ready", s.ManagementAddress()))
Expect(code).Should(Equal(200))
s.Shutdown(nil)
Eventually(stopChan).Should(Receive(Equal(ErrManualStop)))
})
It("Secure And Insecure Ports", func() {
s := CreateHTTPScaffold()
s.SetSecurePort(0)
s.SetKeyFile("./testkeys/clearkey.pem", nil)
s.SetCertFile("./testkeys/clearcert.pem")
stopChan := make(chan error)
err := s.Open()
Expect(err).Should(Succeed())
go func() {
fmt.Fprintf(GinkgoWriter, "Gonna listen on %s and %s\n",
s.InsecureAddress(), s.SecureAddress())
stopErr := s.Listen(&testHandler{})
fmt.Fprintf(GinkgoWriter, "Done listening\n")
stopChan <- stopErr
}()
Eventually(func() bool {
return testGet(s, "")
}, 5*time.Second).Should(BeTrue())
Eventually(func() bool {
return testGetSecure(s, "")
}, time.Second).Should(BeTrue())
shutdownErr := errors.New("Validate")
s.Shutdown(shutdownErr)
Eventually(stopChan).Should(Receive(Equal(shutdownErr)))
})
It("Secure Port Only", func() {
s := CreateHTTPScaffold()
s.SetSecurePort(0)
s.SetInsecurePort(-1)
s.SetKeyFile("./testkeys/clearkey.pem", nil)
s.SetCertFile("./testkeys/clearcert.pem")
stopChan := make(chan error)
err := s.Open()
Expect(err).Should(Succeed())
Expect(s.InsecureAddress()).Should(BeEmpty())
go func() {
fmt.Fprintf(GinkgoWriter, "Gonna listen on %s\n",
s.SecureAddress())
stopErr := s.Listen(&testHandler{})
fmt.Fprintf(GinkgoWriter, "Done listening\n")
stopChan <- stopErr
}()
Eventually(func() bool {
return testGetSecure(s, "")
}, 5*time.Second).Should(BeTrue())
shutdownErr := errors.New("Validate")
s.Shutdown(shutdownErr)
Eventually(stopChan).Should(Receive(Equal(shutdownErr)))
})
It("Secure Port Encrypted Key", func() {
s := CreateHTTPScaffold()
s.SetSecurePort(0)
s.SetInsecurePort(-1)
s.SetKeyFile("./testkeys/serverkey.pem", func() []byte {
return []byte("secure")
})
s.SetCertFile("./testkeys/servercert.pem")
stopChan := make(chan error)
err := s.Open()
Expect(err).Should(Succeed())
Expect(s.InsecureAddress()).Should(BeEmpty())
go func() {
fmt.Fprintf(GinkgoWriter, "Gonna listen on %s\n",
s.SecureAddress())
stopErr := s.Listen(&testHandler{})
fmt.Fprintf(GinkgoWriter, "Done listening\n")
stopChan <- stopErr
}()
Eventually(func() bool {
return testGetSecure(s, "")
}, 5*time.Second).Should(BeTrue())
shutdownErr := errors.New("Validate")
s.Shutdown(shutdownErr)
Eventually(stopChan).Should(Receive(Equal(shutdownErr)))
})
It("Read PEM files", func() {
_, t, err := decodePEM("./testkeys/clearkey.pem", nil)
Expect(err).Should(Succeed())
Expect(t).Should(Equal("RSA PRIVATE KEY"))
_, t, err = decodePEM("./testkeys/clearcert.pem", nil)
Expect(err).Should(Succeed())
Expect(t).Should(Equal("CERTIFICATE"))
_, err = getCertificate("./testkeys/clearcert.pem", "./testkeys/clearkey.pem", nil)
Expect(err).Should(Succeed())
_, _, err = decodePEM("./testkeys/servercert.pem", nil)
Expect(err).Should(Succeed())
_, _, err = decodePEM("./testkeys/serverkey.pem", nil)
Expect(err).ShouldNot(Succeed())
_, _, err = decodePEM("./testkeys/serverkey.pem", func() []byte {
return []byte("notsecure")
})
Expect(err).ShouldNot(Succeed())
_, _, err = decodePEM("./testkeys/serverkey.pem", func() []byte {
return []byte("secure")
})
Expect(err).Should(Succeed())
_, err = getCertificate("./testkeys/servercert.pem", "./testkeys/serverkey.pem", func() []byte {
return []byte("notsecure")
})
})
})
func getText(url string) (int, string) {
req, err := http.NewRequest("GET", url, nil)
Expect(err).Should(Succeed())
req.Header.Set("Accept", "text/plain")
resp, err := http.DefaultClient.Do(req)
Expect(err).Should(Succeed())
defer resp.Body.Close()
bod, err := ioutil.ReadAll(resp.Body)
Expect(err).Should(Succeed())
return resp.StatusCode, string(bod)
}
func getJSON(url string) (int, map[string]string) {
req, err := http.NewRequest("GET", url, nil)
Expect(err).Should(Succeed())
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
Expect(err).Should(Succeed())
defer resp.Body.Close()
bod, err := ioutil.ReadAll(resp.Body)
Expect(err).Should(Succeed())
var vals map[string]string
err = json.Unmarshal(bod, &vals)
Expect(err).Should(Succeed())
return resp.StatusCode, vals
}
func testGet(s *HTTPScaffold, path string) bool {
resp, err := http.Get(fmt.Sprintf("http://%s", s.InsecureAddress()))
if err != nil {
fmt.Fprintf(GinkgoWriter, "Get %s = %s\n", path, err)
return false
}
resp.Body.Close()
if resp.StatusCode != 200 {
fmt.Fprintf(GinkgoWriter, "Get %s = %d\n", path, resp.StatusCode)
return false
}
return true
}
func testGetSecure(s *HTTPScaffold, path string) bool {
resp, err := insecureClient.Get(fmt.Sprintf("https://%s", s.SecureAddress()))
if err != nil {
fmt.Fprintf(GinkgoWriter, "Get %s = %s\n", path, err)
return false
}
resp.Body.Close()
if resp.StatusCode != 200 {
fmt.Fprintf(GinkgoWriter, "Get %s = %d\n", path, resp.StatusCode)
return false
}
return true
}
type testHandler struct {
}
func (h *testHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
var err error
var delayTime time.Duration
delayStr := req.URL.Query().Get("delay")
if delayStr != "" {
delayTime, err = time.ParseDuration(delayStr)
if err != nil {
resp.WriteHeader(http.StatusBadRequest)
return
}
}
if delayTime > 0 {
time.Sleep(delayTime)
}
}