mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
In pure-runner and LB agent, service providers might want to set specific driver options. For example, to add cpu-shares to functions, LB can add the information as extensions to the Call and pass this via gRPC to runners. Runners then pick these extensions from gRPC call and pass it to driver. Using a custom driver implementation, pure-runners can process these extensions to modify docker.CreateContainerOptions. To achieve this, LB agents can now be configured using a call overrider. Pure-runners can be configured using a custom docker driver. RunnerCall and Call interfaces both expose call extensions. An example to demonstrate this is implemented in test/fn-system-tests/system_test.go which registers a call overrider for LB agent as well as a simple custom docker driver. In this example, LB agent adds a key-value to extensions and runners add this key-value as an environment variable to the container.
350 lines
8.2 KiB
Go
350 lines
8.2 KiB
Go
package tests
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
apiutils "github.com/fnproject/fn/test/fn-api-tests"
|
|
sdkmodels "github.com/fnproject/fn_go/models"
|
|
)
|
|
|
|
// See fn-test-utils for json response
|
|
func getEchoContent(respBytes []byte) (string, error) {
|
|
|
|
var respJs map[string]interface{}
|
|
|
|
err := json.Unmarshal(respBytes, &respJs)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
req, ok := respJs["request"].(map[string]interface{})
|
|
if !ok {
|
|
return "", errors.New("unexpected json: request map")
|
|
}
|
|
|
|
echo, ok := req["echoContent"].(string)
|
|
if !ok {
|
|
return "", errors.New("unexpected json: echoContent string")
|
|
}
|
|
|
|
return echo, nil
|
|
}
|
|
|
|
// See fn-test-utils for json response
|
|
func getConfigContent(key string, respBytes []byte) (string, error) {
|
|
|
|
var respJs map[string]interface{}
|
|
|
|
err := json.Unmarshal(respBytes, &respJs)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
cfg, ok := respJs["config"].(map[string]interface{})
|
|
if !ok {
|
|
return "", errors.New("unexpected json: config map")
|
|
}
|
|
|
|
val, ok := cfg[key].(string)
|
|
if !ok {
|
|
return "", fmt.Errorf("unexpected json: %s string", key)
|
|
}
|
|
|
|
return val, nil
|
|
}
|
|
|
|
func TestCanExecuteFunction(t *testing.T) {
|
|
s := apiutils.SetupHarness()
|
|
s.GivenAppExists(t, &sdkmodels.App{Name: s.AppName})
|
|
defer s.Cleanup()
|
|
|
|
rt := s.BasicRoute()
|
|
rt.Image = "fnproject/fn-test-utils"
|
|
rt.Format = "json"
|
|
rt.Memory = 64
|
|
rt.Type = "sync"
|
|
|
|
s.GivenRouteExists(t, s.AppName, rt)
|
|
|
|
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", s.AppName, s.RoutePath)
|
|
|
|
body := `{"echoContent": "HelloWorld", "sleepTime": 0, "isDebug": true}`
|
|
content := bytes.NewBuffer([]byte(body))
|
|
output := &bytes.Buffer{}
|
|
|
|
resp, err := apiutils.CallFN(s.Context, u.String(), content, output, "POST", []string{})
|
|
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)
|
|
}
|
|
}
|
|
|
|
func TestCanExecuteBigOutput(t *testing.T) {
|
|
s := apiutils.SetupHarness()
|
|
s.GivenAppExists(t, &sdkmodels.App{Name: s.AppName})
|
|
defer s.Cleanup()
|
|
|
|
rt := s.BasicRoute()
|
|
rt.Image = "fnproject/fn-test-utils"
|
|
rt.Format = "json"
|
|
rt.Memory = 64
|
|
rt.Type = "sync"
|
|
|
|
s.GivenRouteExists(t, s.AppName, rt)
|
|
|
|
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", s.AppName, s.RoutePath)
|
|
|
|
// Approx 5.3MB output
|
|
body := `{"echoContent": "HelloWorld", "sleepTime": 0, "isDebug": true, "trailerRepeat": 410000}`
|
|
content := bytes.NewBuffer([]byte(body))
|
|
output := &bytes.Buffer{}
|
|
|
|
resp, err := apiutils.CallFN(s.Context, u.String(), content, output, "POST", []string{})
|
|
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) {
|
|
s := apiutils.SetupHarness()
|
|
s.GivenAppExists(t, &sdkmodels.App{Name: s.AppName})
|
|
defer s.Cleanup()
|
|
|
|
rt := s.BasicRoute()
|
|
rt.Image = "fnproject/fn-test-utils"
|
|
rt.Format = "json"
|
|
rt.Memory = 64
|
|
rt.Type = "sync"
|
|
|
|
s.GivenRouteExists(t, s.AppName, rt)
|
|
|
|
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", s.AppName, s.RoutePath)
|
|
|
|
// > 6MB output
|
|
body := `{"echoContent": "HelloWorld", "sleepTime": 0, "isDebug": true, "trailerRepeat": 600000}`
|
|
content := bytes.NewBuffer([]byte(body))
|
|
output := &bytes.Buffer{}
|
|
|
|
resp, err := apiutils.CallFN(s.Context, u.String(), content, output, "POST", []string{})
|
|
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) {
|
|
s := apiutils.SetupHarness()
|
|
s.GivenAppExists(t, &sdkmodels.App{Name: s.AppName})
|
|
defer s.Cleanup()
|
|
|
|
rt := s.BasicRoute()
|
|
rt.Image = "fnproject/fn-test-utils"
|
|
rt.Format = "json"
|
|
rt.Memory = 64
|
|
rt.Type = "sync"
|
|
|
|
s.GivenRouteExists(t, s.AppName, rt)
|
|
|
|
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", s.AppName, s.RoutePath)
|
|
|
|
// empty body output
|
|
body := `{"sleepTime": 0, "isDebug": true, "isEmptyBody": true}`
|
|
content := bytes.NewBuffer([]byte(body))
|
|
output := &bytes.Buffer{}
|
|
|
|
resp, err := apiutils.CallFN(s.Context, u.String(), content, output, "POST", []string{})
|
|
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) {
|
|
|
|
s := apiutils.SetupHarness()
|
|
|
|
s.GivenAppExists(t, &sdkmodels.App{Name: s.AppName})
|
|
defer s.Cleanup()
|
|
|
|
rt := s.BasicRoute()
|
|
rt.Image = "fnproject/fn-test-utils"
|
|
rt.Format = "json"
|
|
rt.Memory = 32
|
|
rt.Type = "sync"
|
|
|
|
s.GivenRouteExists(t, s.AppName, rt)
|
|
|
|
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", s.AppName, s.RoutePath)
|
|
|
|
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 := apiutils.CallFN(s.Context, u.String(), content, output, "POST", []string{})
|
|
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 TestSaturatedSystem(t *testing.T) {
|
|
|
|
s := apiutils.SetupHarness()
|
|
|
|
// override default 60 secs with shorter.
|
|
s.Cancel()
|
|
s.Context, s.Cancel = context.WithTimeout(context.Background(), 4*time.Second)
|
|
|
|
s.GivenAppExists(t, &sdkmodels.App{Name: s.AppName})
|
|
defer s.Cleanup()
|
|
|
|
timeout := int32(1)
|
|
|
|
rt := s.BasicRoute()
|
|
rt.Image = "fnproject/fn-test-utils"
|
|
rt.Format = "json"
|
|
rt.Timeout = &timeout
|
|
rt.Memory = 300
|
|
rt.Type = "sync"
|
|
|
|
s.GivenRouteExists(t, s.AppName, rt)
|
|
|
|
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", s.AppName, s.RoutePath)
|
|
|
|
body := `{"echoContent": "HelloWorld", "sleepTime": 0, "isDebug": true}`
|
|
content := bytes.NewBuffer([]byte(body))
|
|
output := &bytes.Buffer{}
|
|
|
|
resp, err := apiutils.CallFN(s.Context, u.String(), content, output, "POST", []string{})
|
|
if resp != nil || err == nil || s.Context.Err() == nil {
|
|
t.Fatalf("Expected response: %v err:%v", resp, err)
|
|
}
|
|
}
|