mirror of
https://github.com/sablierapp/sablier.git
synced 2026-01-01 18:47:23 +01:00
Add 'plugins/traefik/' from commit 'aef1f9e0dd205ea9cdea9e3ccf11900c5fe79b1f'
git-subtree-dir: plugins/traefik git-subtree-mainline:1a14070131git-subtree-split:aef1f9e0dd
This commit is contained in:
59
plugins/traefik/pkg/strategy/blocking_strategy.go
Normal file
59
plugins/traefik/pkg/strategy/blocking_strategy.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BlockingStrategy struct {
|
||||
Requests []string
|
||||
Name string
|
||||
Next http.Handler
|
||||
Timeout time.Duration
|
||||
BlockDelay time.Duration
|
||||
BlockCheckInterval time.Duration
|
||||
}
|
||||
|
||||
type InternalServerError struct {
|
||||
ServiceName string `json:"serviceName"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// ServeHTTP retrieve the service status
|
||||
func (e *BlockingStrategy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
for start := time.Now(); time.Since(start) < e.BlockDelay; {
|
||||
notReadyCount := 0
|
||||
for _, request := range e.Requests {
|
||||
|
||||
log.Printf("Sending request: %s", request)
|
||||
status, err := getServiceStatus(request)
|
||||
log.Printf("Status: %s", status)
|
||||
|
||||
if err != nil {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(rw).Encode(InternalServerError{ServiceName: e.Name, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if status != "started" {
|
||||
notReadyCount++
|
||||
}
|
||||
}
|
||||
if notReadyCount == 0 {
|
||||
// Services all started forward request
|
||||
e.Next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(e.BlockCheckInterval)
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusServiceUnavailable)
|
||||
json.NewEncoder(rw).Encode(InternalServerError{ServiceName: e.Name, Error: fmt.Sprintf("Service was unreachable within %s", e.BlockDelay)})
|
||||
}
|
||||
88
plugins/traefik/pkg/strategy/blocking_strategy_test.go
Normal file
88
plugins/traefik/pkg/strategy/blocking_strategy_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSingleBlockingStrategy_ServeHTTP(t *testing.T) {
|
||||
for _, test := range SingleServiceTestCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("ok"))
|
||||
})
|
||||
|
||||
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(test.onDemandServiceResponses[0].status)
|
||||
fmt.Fprint(w, test.onDemandServiceResponses[0].body)
|
||||
}))
|
||||
|
||||
defer mockServer.Close()
|
||||
|
||||
blockingStrategy := &BlockingStrategy{
|
||||
Name: "whoami",
|
||||
Requests: []string{mockServer.URL},
|
||||
Next: next,
|
||||
BlockDelay: 1 * time.Second,
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://mydomain/whoami", nil)
|
||||
|
||||
blockingStrategy.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, test.expected.blocking, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleBlockingStrategy_ServeHTTP(t *testing.T) {
|
||||
|
||||
for _, test := range MultipleServicesTestCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
urls := make([]string, len(test.onDemandServiceResponses))
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("ok"))
|
||||
})
|
||||
|
||||
for responseIndex, response := range test.onDemandServiceResponses {
|
||||
response := response
|
||||
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(response.status)
|
||||
fmt.Fprint(w, response.body)
|
||||
}))
|
||||
|
||||
defer mockServer.Close()
|
||||
urls[responseIndex] = mockServer.URL
|
||||
}
|
||||
fmt.Println(urls)
|
||||
blockingStrategy := &BlockingStrategy{
|
||||
Name: "whoami",
|
||||
Requests: urls,
|
||||
Next: next,
|
||||
BlockDelay: 1 * time.Second,
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://mydomain/whoami", nil)
|
||||
|
||||
blockingStrategy.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, test.expected.blocking, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
62
plugins/traefik/pkg/strategy/dynamic_strategy.go
Normal file
62
plugins/traefik/pkg/strategy/dynamic_strategy.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/acouvreur/traefik-ondemand-plugin/pkg/pages"
|
||||
)
|
||||
|
||||
type DynamicStrategy struct {
|
||||
Requests []string
|
||||
Name string
|
||||
Next http.Handler
|
||||
Timeout time.Duration
|
||||
DisplayName string
|
||||
LoadingPage string
|
||||
ErrorPage string
|
||||
}
|
||||
|
||||
// ServeHTTP retrieve the service status
|
||||
func (e *DynamicStrategy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
started := make([]bool, len(e.Requests))
|
||||
|
||||
displayName := e.Name
|
||||
if len(e.DisplayName) > 0 {
|
||||
displayName = e.DisplayName
|
||||
}
|
||||
|
||||
notReadyCount := 0
|
||||
for requestIndex, request := range e.Requests {
|
||||
log.Printf("Sending request: %s", request)
|
||||
status, err := getServiceStatus(request)
|
||||
log.Printf("Status: %s", status)
|
||||
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
rw.Write([]byte(pages.GetErrorPage(e.ErrorPage, displayName, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
if status == "started" {
|
||||
started[requestIndex] = true
|
||||
} else if status == "starting" {
|
||||
started[requestIndex] = false
|
||||
notReadyCount++
|
||||
} else {
|
||||
// Error
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
rw.Write([]byte(pages.GetErrorPage(e.ErrorPage, displayName, status)))
|
||||
return
|
||||
}
|
||||
}
|
||||
if notReadyCount == 0 {
|
||||
// All services are ready, forward request
|
||||
e.Next.ServeHTTP(rw, req)
|
||||
} else {
|
||||
// Services still starting, notify client
|
||||
rw.WriteHeader(http.StatusAccepted)
|
||||
rw.Write([]byte(pages.GetLoadingPage(e.LoadingPage, displayName, e.Timeout)))
|
||||
}
|
||||
}
|
||||
77
plugins/traefik/pkg/strategy/dynamic_strategy_test.go
Normal file
77
plugins/traefik/pkg/strategy/dynamic_strategy_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSingleDynamicStrategy_ServeHTTP(t *testing.T) {
|
||||
|
||||
for _, test := range SingleServiceTestCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, test.onDemandServiceResponses[0].body)
|
||||
}))
|
||||
|
||||
defer mockServer.Close()
|
||||
|
||||
dynamicStrategy := &DynamicStrategy{
|
||||
Name: "whoami",
|
||||
Requests: []string{mockServer.URL},
|
||||
Next: next,
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://mydomain/whoami", nil)
|
||||
|
||||
dynamicStrategy.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, test.expected.dynamic, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleDynamicStrategy_ServeHTTP(t *testing.T) {
|
||||
for _, test := range MultipleServicesTestCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
urls := make([]string, len(test.onDemandServiceResponses))
|
||||
for responseIndex, response := range test.onDemandServiceResponses {
|
||||
response := response
|
||||
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, response.body)
|
||||
}))
|
||||
defer mockServer.Close()
|
||||
|
||||
urls[responseIndex] = mockServer.URL
|
||||
}
|
||||
dynamicStrategy := &DynamicStrategy{
|
||||
Name: "whoami",
|
||||
Requests: urls,
|
||||
Next: next,
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://mydomain/whoami", nil)
|
||||
|
||||
dynamicStrategy.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, test.expected.dynamic, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
39
plugins/traefik/pkg/strategy/strategy.go
Normal file
39
plugins/traefik/pkg/strategy/strategy.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Net client is a custom client to timeout after 2 seconds if the service is not ready
|
||||
var netClient = &http.Client{
|
||||
Timeout: time.Second * 2,
|
||||
}
|
||||
|
||||
type Strategy interface {
|
||||
ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
||||
}
|
||||
|
||||
func getServiceStatus(request string) (string, error) {
|
||||
|
||||
// This request wakes up the service if he's scaled to 0
|
||||
resp, err := netClient.Get(request)
|
||||
if err != nil {
|
||||
return "error", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "parsing error", err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return "error from ondemand service", errors.New(string(body))
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(string(body), "\n"), nil
|
||||
}
|
||||
135
plugins/traefik/pkg/strategy/strategy_test_cases.go
Normal file
135
plugins/traefik/pkg/strategy/strategy_test_cases.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package strategy
|
||||
|
||||
type OnDemandServiceResponses struct {
|
||||
body string
|
||||
status int
|
||||
}
|
||||
type ExpectedStatusForStrategy struct {
|
||||
dynamic int
|
||||
blocking int
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
desc string
|
||||
onDemandServiceResponses []OnDemandServiceResponses
|
||||
expected ExpectedStatusForStrategy
|
||||
}
|
||||
|
||||
var SingleServiceTestCases = []TestCase{
|
||||
{
|
||||
desc: "service is / keeps on starting",
|
||||
onDemandServiceResponses: GenerateServicesResponses(1, "starting"),
|
||||
expected: ExpectedStatusForStrategy{
|
||||
dynamic: 202,
|
||||
blocking: 503,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "service is started",
|
||||
onDemandServiceResponses: GenerateServicesResponses(1, "started"),
|
||||
expected: ExpectedStatusForStrategy{
|
||||
dynamic: 200,
|
||||
blocking: 200,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "ondemand service is in error",
|
||||
onDemandServiceResponses: GenerateServicesResponses(1, "error"),
|
||||
expected: ExpectedStatusForStrategy{
|
||||
dynamic: 500,
|
||||
blocking: 500,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func GenerateServicesResponses(count int, serviceBody string) []OnDemandServiceResponses {
|
||||
responses := make([]OnDemandServiceResponses, count)
|
||||
for i := 0; i < count; i++ {
|
||||
if serviceBody == "starting" || serviceBody == "started" {
|
||||
responses[i] = OnDemandServiceResponses{
|
||||
body: serviceBody,
|
||||
status: 200,
|
||||
}
|
||||
} else {
|
||||
responses[i] = OnDemandServiceResponses{
|
||||
body: serviceBody,
|
||||
status: 503,
|
||||
}
|
||||
}
|
||||
}
|
||||
return responses
|
||||
}
|
||||
|
||||
var MultipleServicesTestCases = []TestCase{
|
||||
{
|
||||
desc: "all services are starting",
|
||||
onDemandServiceResponses: GenerateServicesResponses(5, "starting"),
|
||||
expected: ExpectedStatusForStrategy{
|
||||
dynamic: 202,
|
||||
blocking: 503,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one started others are starting",
|
||||
onDemandServiceResponses: append(GenerateServicesResponses(1, "starting"), GenerateServicesResponses(4, "started")...),
|
||||
expected: ExpectedStatusForStrategy{
|
||||
dynamic: 202,
|
||||
blocking: 503,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one starting others are started",
|
||||
onDemandServiceResponses: append(GenerateServicesResponses(4, "starting"), GenerateServicesResponses(1, "started")...),
|
||||
expected: ExpectedStatusForStrategy{
|
||||
dynamic: 202,
|
||||
blocking: 503,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one errored others are starting",
|
||||
onDemandServiceResponses: append(
|
||||
GenerateServicesResponses(2, "starting"),
|
||||
append(
|
||||
GenerateServicesResponses(1, "error"),
|
||||
GenerateServicesResponses(2, "starting")...,
|
||||
)...,
|
||||
),
|
||||
expected: ExpectedStatusForStrategy{
|
||||
dynamic: 500,
|
||||
blocking: 500,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one errored others are started",
|
||||
onDemandServiceResponses: append(
|
||||
GenerateServicesResponses(1, "error"),
|
||||
GenerateServicesResponses(4, "started")...,
|
||||
),
|
||||
expected: ExpectedStatusForStrategy{
|
||||
dynamic: 500,
|
||||
blocking: 500,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one errored others are mix of starting / started",
|
||||
onDemandServiceResponses: append(
|
||||
GenerateServicesResponses(2, "started"),
|
||||
append(
|
||||
GenerateServicesResponses(1, "error"),
|
||||
GenerateServicesResponses(2, "starting")...,
|
||||
)...,
|
||||
),
|
||||
expected: ExpectedStatusForStrategy{
|
||||
dynamic: 500,
|
||||
blocking: 500,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all are started",
|
||||
onDemandServiceResponses: GenerateServicesResponses(5, "started"),
|
||||
expected: ExpectedStatusForStrategy{
|
||||
dynamic: 200,
|
||||
blocking: 200,
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user