Files
fn-serverless/test/fn-system-tests/exec_route_test.go
Tolga Ceylan aa13a40168 fn: agent/lb/runner error handling adjustments (#1214)
1) Early call validation and return due to cpu/mem impossible
to meet (eg. request cpu/mem larger than max-mem or max-cpu
on server) now emits HTTP Bad Request (400) instead of 503.
This case is most likely due to client/service configuration
and/or validation issue.
2) 'failed' metric is now removed. 'failed' versus 'errors'
were too confusing. 'errors' is now a catch all error case.
3) new 'canceled' counter for client side cancels.
4) 'server_busy' now covers more cases than it previously did.
2018-09-14 16:50:14 -07:00

309 lines
7.4 KiB
Go

package tests
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"path"
"strings"
"testing"
"time"
"github.com/fnproject/fn/api/models"
)
// TODO deprecate with routes
func TestCanExecuteFunction(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
rt := ensureRoute(t)
lb, err := LB()
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
u := url.URL{
Scheme: "http",
Host: lb,
}
u.Path = path.Join(u.Path, "r", appName, rt.Path)
body := `{"echoContent": "HelloWorld", "sleepTime": 0, "isDebug": true}`
content := bytes.NewBuffer([]byte(body))
output := &bytes.Buffer{}
resp, err := callFN(ctx, u.String(), content, output, "POST")
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
echo, err := getEchoContent(output.Bytes())
if err != nil || echo != "HelloWorld" {
t.Fatalf("getEchoContent/HelloWorld check failed on %v", output)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("StatusCode check failed on %v", resp.StatusCode)
}
// Now let's check FN_CHEESE, since LB and runners have override/extension mechanism
// to insert FN_CHEESE into config
cheese, err := getConfigContent("FN_CHEESE", output.Bytes())
if err != nil || cheese != "Tete de Moine" {
t.Fatalf("getConfigContent/FN_CHEESE check failed (%v) on %v", err, output)
}
// Now let's check FN_WINE, since runners have override to insert this.
wine, err := getConfigContent("FN_WINE", output.Bytes())
if err != nil || wine != "1982 Margaux" {
t.Fatalf("getConfigContent/FN_WINE check failed (%v) on %v", err, output)
}
}
func TestCanExecuteBigOutput(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
rt := ensureRoute(t)
lb, err := LB()
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
u := url.URL{
Scheme: "http",
Host: lb,
}
u.Path = path.Join(u.Path, "r", appName, rt.Path)
// Approx 5.3MB output
body := `{"echoContent": "HelloWorld", "sleepTime": 0, "isDebug": true, "trailerRepeat": 410000}`
content := bytes.NewBuffer([]byte(body))
output := &bytes.Buffer{}
resp, err := callFN(ctx, u.String(), content, output, "POST")
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
t.Logf("getEchoContent/HelloWorld size %d", len(output.Bytes()))
echo, err := getEchoContent(output.Bytes())
if err != nil || echo != "HelloWorld" {
t.Fatalf("getEchoContent/HelloWorld check failed on %v", output)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("StatusCode check failed on %v", resp.StatusCode)
}
}
func TestCanExecuteTooBigOutput(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
rt := ensureRoute(t)
lb, err := LB()
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
u := url.URL{
Scheme: "http",
Host: lb,
}
u.Path = path.Join(u.Path, "r", appName, rt.Path)
// > 6MB output
body := `{"echoContent": "HelloWorld", "sleepTime": 0, "isDebug": true, "trailerRepeat": 600000}`
content := bytes.NewBuffer([]byte(body))
output := &bytes.Buffer{}
resp, err := callFN(ctx, u.String(), content, output, "POST")
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
exp := "{\"error\":{\"message\":\"function response too large\"}}\n"
actual := output.String()
if !strings.Contains(exp, actual) || len(exp) != len(actual) {
t.Fatalf("Assertion error.\n\tExpected: %v\n\tActual: %v", exp, output.String())
}
if resp.StatusCode != http.StatusBadGateway {
t.Fatalf("StatusCode check failed on %v", resp.StatusCode)
}
}
func TestCanExecuteEmptyOutput(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
rt := ensureRoute(t)
lb, err := LB()
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
u := url.URL{
Scheme: "http",
Host: lb,
}
u.Path = path.Join(u.Path, "r", appName, rt.Path)
// empty body output
body := `{"sleepTime": 0, "isDebug": true, "isEmptyBody": true}`
content := bytes.NewBuffer([]byte(body))
output := &bytes.Buffer{}
resp, err := callFN(ctx, u.String(), content, output, "POST")
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
actual := output.String()
if 0 != len(actual) {
t.Fatalf("Assertion error.\n\tExpected empty\n\tActual: %v", output.String())
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("StatusCode check failed on %v", resp.StatusCode)
}
}
func TestBasicConcurrentExecution(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
rt := ensureRoute(t)
lb, err := LB()
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
u := url.URL{
Scheme: "http",
Host: lb,
}
u.Path = path.Join(u.Path, "r", appName, rt.Path)
results := make(chan error)
concurrentFuncs := 10
for i := 0; i < concurrentFuncs; i++ {
go func() {
body := `{"echoContent": "HelloWorld", "sleepTime": 0, "isDebug": true}`
content := bytes.NewBuffer([]byte(body))
output := &bytes.Buffer{}
resp, err := callFN(ctx, u.String(), content, output, "POST")
if err != nil {
results <- fmt.Errorf("Got unexpected error: %v", err)
return
}
echo, err := getEchoContent(output.Bytes())
if err != nil || echo != "HelloWorld" {
results <- fmt.Errorf("Assertion error.\n\tActual: %v", output.String())
return
}
if resp.StatusCode != http.StatusOK {
results <- fmt.Errorf("StatusCode check failed on %v", resp.StatusCode)
return
}
results <- nil
}()
}
for i := 0; i < concurrentFuncs; i++ {
err := <-results
if err != nil {
t.Fatalf("Error in basic concurrency execution test: %v", err)
}
}
}
func callFN(ctx context.Context, u string, content io.Reader, output io.Writer, method string) (*http.Response, error) {
if method == "" {
if content == nil {
method = "GET"
} else {
method = "POST"
}
}
req, err := http.NewRequest(method, u, content)
if err != nil {
return nil, fmt.Errorf("error running route: %s", err)
}
req.Header.Set("Content-Type", "application/json")
req = req.WithContext(ctx)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("error running route: %s", err)
}
io.Copy(output, resp.Body)
return resp, nil
}
func ensureRoute(t *testing.T, rts ...*models.Route) *models.Route {
var rt *models.Route
if len(rts) > 0 {
rt = rts[0]
} else {
rt = &models.Route{
Path: routeName + "yabbadabbadoo",
Image: image,
Format: format,
Memory: memory,
Type: typ,
}
}
var wrapped struct {
Route *models.Route `json:"route"`
}
wrapped.Route = rt
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(wrapped)
if err != nil {
t.Fatal("error encoding body", err)
}
urlStr := host() + "/v1/apps/" + appName + "/routes" + rt.Path
u, err := url.Parse(urlStr)
if err != nil {
t.Fatal("error creating url", urlStr, err)
}
req, err := http.NewRequest("PUT", u.String(), &buf)
if err != nil {
t.Fatal("error creating request", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal("error creating route", err)
}
buf.Reset()
io.Copy(&buf, resp.Body)
if resp.StatusCode != 200 {
t.Fatal("error creating/updating app or otherwise ensuring it exists:", resp.StatusCode, buf.String())
}
wrapped.Route = nil
err = json.NewDecoder(&buf).Decode(&wrapped)
if err != nil {
t.Fatal("error decoding response")
}
return wrapped.Route
}