mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
* App ID * Clean-up * Use ID or name to reference apps * Can use app by name or ID * Get rid of AppName for routes API and model routes API is completely backwards-compatible routes API accepts both app ID and name * Get rid of AppName from calls API and model * Fixing tests * Get rid of AppName from logs API and model * Restrict API to work with app names only * Addressing review comments * Fix for hybrid mode * Fix rebase problems * Addressing review comments * Addressing review comments pt.2 * Fixing test issue * Addressing review comments pt.3 * Updated docstring * Adjust UpdateApp SQL implementation to work with app IDs instead of names * Fixing tests * fmt after rebase * Make tests green again! * Use GetAppByID wherever it is necessary - adding new v2 endpoints to keep hybrid api/runner mode working - extract CallBase from Call object to expose that to a user (it doesn't include any app reference, as we do for all other API objects) * Get rid of GetAppByName * Adjusting server router setup * Make hybrid work again * Fix datastore tests * Fixing tests * Do not ignore app_id * Resolve issues after rebase * Updating test to make it work as it was * Tabula rasa for migrations * Adding calls API test - we need to ensure we give "App not found" for the missing app and missing call in first place - making previous test work (request missing call for the existing app) * Make datastore tests work fine with correctly applied migrations * Make CallFunction middleware work again had to adjust its implementation to set app ID before proceeding * The biggest rebase ever made * Fix 8's migration * Fix tests * Fix hybrid client * Fix tests problem * Increment app ID migration version * Fixing TestAppUpdate * Fix rebase issues * Addressing review comments * Renew vendor * Updated swagger doc per recommendations
315 lines
6.6 KiB
Go
315 lines
6.6 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fnproject/fn/api/id"
|
|
"github.com/fnproject/fn/api/models"
|
|
)
|
|
|
|
type testSlot struct {
|
|
id uint64
|
|
err error
|
|
isClosed bool
|
|
}
|
|
|
|
func (a *testSlot) exec(ctx context.Context, call *call) error {
|
|
return nil
|
|
}
|
|
|
|
func (a *testSlot) Close(ctx context.Context) error {
|
|
if a.isClosed {
|
|
panic(fmt.Errorf("id=%d already closed %v", a.id, a))
|
|
}
|
|
a.isClosed = true
|
|
return nil
|
|
}
|
|
|
|
func (a *testSlot) Error() error {
|
|
return a.err
|
|
}
|
|
|
|
func NewTestSlot(id uint64) Slot {
|
|
mySlot := &testSlot{
|
|
id: id,
|
|
}
|
|
return mySlot
|
|
}
|
|
|
|
func checkGetTokenId(t *testing.T, a *slotQueue, dur time.Duration, id uint64) error {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), dur)
|
|
defer cancel()
|
|
|
|
outChan := a.startDequeuer(ctx)
|
|
|
|
for {
|
|
select {
|
|
case z := <-outChan:
|
|
if !a.acquireSlot(z) {
|
|
continue
|
|
}
|
|
|
|
z.slot.Close(ctx)
|
|
|
|
if z.id != id {
|
|
return fmt.Errorf("Bad slotToken received: %#v expected: %d", z, id)
|
|
}
|
|
return nil
|
|
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSlotQueueBasic1(t *testing.T) {
|
|
|
|
maxId := uint64(10)
|
|
slotName := "test1"
|
|
|
|
slots := make([]Slot, 0, maxId)
|
|
tokens := make([]*slotToken, 0, maxId)
|
|
|
|
obj := NewSlotQueue(slotName)
|
|
|
|
timeout := time.Duration(500) * time.Millisecond
|
|
err := checkGetTokenId(t, obj, timeout, 6)
|
|
if err == nil {
|
|
t.Fatalf("Should not get anything from queue")
|
|
}
|
|
if err != context.DeadlineExceeded {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
// create slots
|
|
for id := uint64(0); id < maxId; id += 1 {
|
|
slots = append(slots, NewTestSlot(id))
|
|
}
|
|
|
|
// queue a few slots here
|
|
for id := uint64(0); id < maxId; id += 1 {
|
|
tok := obj.queueSlot(slots[id])
|
|
|
|
innerTok := tok.slot.(*testSlot)
|
|
|
|
// check for slot id match
|
|
if innerTok != slots[id] {
|
|
t.Fatalf("queued testSlot does not match with slotToken.slot %#v vs %#v", innerTok, slots[id])
|
|
}
|
|
|
|
tokens = append(tokens, tok)
|
|
}
|
|
|
|
// Now according to LIFO semantics, we should get 9,8,7,6,5,4,3,2,1,0 if we dequeued right now.
|
|
// but let's acquire 9
|
|
if !obj.acquireSlot(tokens[9]) {
|
|
t.Fatalf("Cannot acquire slotToken: %#v", tokens[9])
|
|
}
|
|
// let acquire 0
|
|
if !obj.acquireSlot(tokens[0]) {
|
|
t.Fatalf("Cannot acquire slotToken: %#v", tokens[0])
|
|
}
|
|
// let acquire 5
|
|
if !obj.acquireSlot(tokens[5]) {
|
|
t.Fatalf("Cannot acquire slotToken: %#v", tokens[5])
|
|
}
|
|
// try acquire 5 again, it should fail
|
|
if obj.acquireSlot(tokens[5]) {
|
|
t.Fatalf("Shouldn't be able to acquire slotToken: %#v", tokens[5])
|
|
}
|
|
|
|
err = checkGetTokenId(t, obj, timeout, 8)
|
|
if err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
// acquire 7 before we can consume
|
|
if !obj.acquireSlot(tokens[7]) {
|
|
t.Fatalf("Cannot acquire slotToken: %#v", tokens[2])
|
|
}
|
|
|
|
err = checkGetTokenId(t, obj, timeout, 6)
|
|
if err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSlotQueueBasic2(t *testing.T) {
|
|
|
|
obj := NewSlotQueue("test2")
|
|
|
|
if !obj.isIdle() {
|
|
t.Fatalf("Should be idle")
|
|
}
|
|
|
|
timeout := time.Duration(500) * time.Millisecond
|
|
err := checkGetTokenId(t, obj, timeout, 6)
|
|
if err == nil {
|
|
t.Fatalf("Should not get anything from queue")
|
|
}
|
|
if err != context.DeadlineExceeded {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
}
|
|
|
|
func statsHelperSet(reqW, reqE, conW, conS, conI, conB uint64) slotQueueStats {
|
|
return slotQueueStats{
|
|
requestStates: [RequestStateMax]uint64{0, reqW, reqE, 0},
|
|
containerStates: [ContainerStateMax]uint64{0, conW, conS, conI, conB, 0},
|
|
}
|
|
}
|
|
|
|
func TestSlotNewContainerLogic1(t *testing.T) {
|
|
|
|
var cur slotQueueStats
|
|
|
|
cur = statsHelperSet(0, 0, 0, 0, 0, 0)
|
|
// CASE: There's no queued requests
|
|
if isNewContainerNeeded(&cur) {
|
|
t.Fatalf("Should not need a new container cur: %#v", cur)
|
|
}
|
|
|
|
// CASE: There are starters >= queued requests
|
|
cur = statsHelperSet(1, 0, 0, 10, 0, 0)
|
|
if isNewContainerNeeded(&cur) {
|
|
t.Fatalf("Should not need a new container cur: %#v", cur)
|
|
}
|
|
|
|
// CASE: There are starters < queued requests
|
|
cur = statsHelperSet(10, 0, 0, 1, 0, 0)
|
|
if !isNewContainerNeeded(&cur) {
|
|
t.Fatalf("Should need a new container cur: %#v", cur)
|
|
}
|
|
|
|
// CASE: effective queued requests (idle > requests)
|
|
cur = statsHelperSet(10, 0, 0, 0, 11, 0)
|
|
if isNewContainerNeeded(&cur) {
|
|
t.Fatalf("Should not need a new container cur: %#v", cur)
|
|
}
|
|
|
|
// CASE: effective queued requests (idle < requests)
|
|
cur = statsHelperSet(10, 0, 0, 0, 5, 0)
|
|
if !isNewContainerNeeded(&cur) {
|
|
t.Fatalf("Should need a new container cur: %#v", cur)
|
|
}
|
|
|
|
// CASE: no executors, but 1 queued request
|
|
cur = statsHelperSet(1, 0, 0, 0, 0, 0)
|
|
if !isNewContainerNeeded(&cur) {
|
|
t.Fatalf("Should need a new container cur: %#v", cur)
|
|
}
|
|
}
|
|
|
|
func TestSlotQueueBasic3(t *testing.T) {
|
|
|
|
slotName := "test3"
|
|
|
|
obj := NewSlotQueue(slotName)
|
|
|
|
slot1 := NewTestSlot(1)
|
|
slot2 := NewTestSlot(2)
|
|
token1 := obj.queueSlot(slot1)
|
|
obj.queueSlot(slot2)
|
|
|
|
timeout := time.Duration(500) * time.Millisecond
|
|
err := checkGetTokenId(t, obj, timeout, 1)
|
|
if err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
// let's acquire 1
|
|
if !obj.acquireSlot(token1) {
|
|
t.Fatalf("should fail to acquire %#v", token1)
|
|
}
|
|
|
|
goMax := 10
|
|
out := make(chan error, goMax)
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(goMax)
|
|
for i := 0; i < goMax; i += 1 {
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
err := checkGetTokenId(t, obj, timeout, 1)
|
|
out <- err
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
deadlineErrors := 0
|
|
for i := 0; i < goMax; i += 1 {
|
|
err := <-out
|
|
if err == context.DeadlineExceeded {
|
|
deadlineErrors++
|
|
} else if err == nil {
|
|
t.Fatalf("Unexpected success")
|
|
} else {
|
|
t.Fatalf("Unexpected error: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
if deadlineErrors != goMax {
|
|
t.Fatalf("Expected %d got %d deadline exceeded errors", goMax, deadlineErrors)
|
|
}
|
|
|
|
err = checkGetTokenId(t, obj, timeout, 2)
|
|
if err != context.DeadlineExceeded {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
}
|
|
|
|
func BenchmarkSlotKey(b *testing.B) {
|
|
appName := "myapp"
|
|
appID := id.New().String()
|
|
path := "/"
|
|
image := "fnproject/fn-test-utils"
|
|
const timeout = 1
|
|
const idleTimeout = 20
|
|
const memory = 256
|
|
CPUs := models.MilliCPUs(1000)
|
|
method := "GET"
|
|
url := "http://127.0.0.1:8080/r/" + appName + path
|
|
payload := "payload"
|
|
typ := "sync"
|
|
format := "default"
|
|
cfg := models.Config{
|
|
"FN_FORMAT": format,
|
|
"FN_APP_NAME": appName,
|
|
"FN_PATH": path,
|
|
"FN_MEMORY": strconv.Itoa(memory),
|
|
"FN_CPUS": CPUs.String(),
|
|
"FN_TYPE": typ,
|
|
"APP_VAR": "FOO",
|
|
"ROUTE_VAR": "BAR",
|
|
}
|
|
|
|
cm := &models.Call{
|
|
Config: cfg,
|
|
AppID: appID,
|
|
Path: path,
|
|
Image: image,
|
|
Type: typ,
|
|
Format: format,
|
|
Timeout: timeout,
|
|
IdleTimeout: idleTimeout,
|
|
Memory: memory,
|
|
CPUs: CPUs,
|
|
Payload: payload,
|
|
URL: url,
|
|
Method: method,
|
|
}
|
|
|
|
call := &call{Call: cm}
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_ = getSlotQueueKey(call)
|
|
}
|
|
}
|