HTTP Triggers hookup (#1086)

* Initial suypport for invoking tiggers

* dupe method

* tighten server constraints

* runner tests not working yet

* basic route tests passing

* post rebase fixes

* add hybrid support for trigger invoke and tests

* consoloidate all hybrid evil into one place

* cleanup and make triggers unique by source

* fix oops with Agent

* linting

* review fixes
This commit is contained in:
Owen Cliffe
2018-07-05 18:56:07 +01:00
committed by Reed Allman
parent b07a000a18
commit b8b544ed25
38 changed files with 2208 additions and 865 deletions

View File

@@ -11,7 +11,6 @@ import (
"log"
"math/rand"
"sort"
"sync/atomic"
"testing"
"time"
@@ -52,18 +51,21 @@ type ResourceProvider interface {
// BasicResourceProvider supplies simple objects and can be used as a base for custom resource providers
type BasicResourceProvider struct {
idCount uint32
rand *rand.Rand
}
// DataStoreFunc provides an instance of a data store
type DataStoreFunc func(*testing.T) models.Datastore
//NewBasicResourceProvider creates a dumb resource provider that generates resources that have valid, random names (and other unique attributes)
func NewBasicResourceProvider() ResourceProvider {
return &BasicResourceProvider{}
return &BasicResourceProvider{
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
}
}
func (brp *BasicResourceProvider) NextID() uint32 {
return atomic.AddUint32(&brp.idCount, rand.Uint32())
return brp.rand.Uint32()
}
func (brp *BasicResourceProvider) DefaultCtx() context.Context {
@@ -86,7 +88,7 @@ func (brp *BasicResourceProvider) ValidTrigger(appId, funcId string) *models.Tri
AppID: appId,
FnID: funcId,
Type: "http",
Source: "ASource",
Source: fmt.Sprintf("/source_%09d", brp.NextID()),
}
return trigger
@@ -1241,7 +1243,24 @@ func RunTriggersTest(t *testing.T, dsf DataStoreFunc, rp ResourceProvider) {
t.Fatalf("expected empty trigger list and no error, but got list [%v] and err %s", triggers.Items, err)
}
})
t.Run("duplicate trigger source of same type on same app", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
app := h.GivenAppInDb(rp.ValidApp())
fn := h.GivenFnInDb(rp.ValidFn(app.ID))
origT := h.GivenTriggerInDb(rp.ValidTrigger(app.ID, fn.ID))
newT := rp.ValidTrigger(app.ID, fn.ID)
newT.Source = origT.Source
_, err := ds.InsertTrigger(ctx, newT)
if err != models.ErrTriggerSourceExists {
t.Errorf("Expecting to fail with duplicate source on same app, got %s", err)
}
//todo ensure this doesn't apply when type is not equal
})
t.Run("app id not same as fn id ", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
@@ -1532,6 +1551,39 @@ func RunTriggersTest(t *testing.T, dsf DataStoreFunc, rp ResourceProvider) {
})
}
func RunTriggerBySourceTests(t *testing.T, dsf DataStoreFunc, rp ResourceProvider) {
t.Run("http_trigger_access", func(t *testing.T) {
ds := dsf(t)
ctx := rp.DefaultCtx()
t.Run("get_non_existant_trigger", func(t *testing.T) {
_, err := ds.GetTriggerBySource(ctx, "none", "http", "source")
if err != models.ErrTriggerNotFound {
t.Fatalf("Expecting trigger not found, got %s", err)
}
})
t.Run("get_trigger_specific_http_route", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
testTrigger := h.GivenTriggerInDb(rp.ValidTrigger(testApp.ID, testFn.ID))
trigger, err := ds.GetTriggerBySource(ctx, testApp.ID, testTrigger.Type, testTrigger.Source)
if err != nil {
t.Fatalf("Expecting trigger, got error %s", err)
}
if !trigger.Equals(testTrigger) {
t.Errorf("Expecting trigger %#v got %#v", testTrigger, trigger)
}
})
})
}
func RunAllTests(t *testing.T, dsf DataStoreFunc, rp ResourceProvider) {
buf := setLogBuffer()
defer func() {
@@ -1544,5 +1596,6 @@ func RunAllTests(t *testing.T, dsf DataStoreFunc, rp ResourceProvider) {
RunRoutesTest(t, dsf, rp)
RunFnsTest(t, dsf, rp)
RunTriggersTest(t, dsf, rp)
RunTriggerBySourceTests(t, dsf, rp)
}

View File

@@ -16,6 +16,12 @@ type metricds struct {
ds models.Datastore
}
func (m *metricds) GetTriggerBySource(ctx context.Context, appId string, triggerType, source string) (*models.Trigger, error) {
ctx, span := trace.StartSpan(ctx, "ds_get_trigger_by_source")
defer span.End()
return m.ds.GetTriggerBySource(ctx, appId, triggerType, source)
}
func (m *metricds) GetAppID(ctx context.Context, appName string) (string, error) {
ctx, span := trace.StartSpan(ctx, "ds_get_app_id")
defer span.End()

View File

@@ -28,6 +28,18 @@ func NewMock() models.Datastore {
return NewMockInit()
}
var _ models.Datastore = &mock{}
func (m *mock) GetTriggerBySource(ctx context.Context, appId string, triggerType, source string) (*models.Trigger, error) {
for _, t := range m.Triggers {
if t.AppID == appId && t.Type == triggerType && t.Source == source {
return t, nil
}
}
return nil, models.ErrTriggerNotFound
}
// args helps break tests less if we change stuff
func NewMockInit(args ...interface{}) models.Datastore {
var mocker mock
@@ -416,6 +428,12 @@ func (m *mock) InsertTrigger(ctx context.Context, trigger *models.Trigger) (*mod
t.Name == trigger.Name) {
return nil, models.ErrTriggerExists
}
if t.AppID == trigger.AppID &&
t.Source == trigger.Source &&
t.Type == trigger.Type {
return nil, models.ErrTriggerSourceExists
}
}
cl := trigger.Clone()

View File

@@ -127,6 +127,8 @@ const (
triggerSelector = `SELECT id,name,app_id,fn_id,type,source,annotations,created_at,updated_at FROM triggers`
triggerIDSelector = triggerSelector + ` WHERE id=?`
triggerIDSourceSelector = triggerSelector + ` WHERE app_id=? AND type=? AND source=?`
EnvDBPingMaxRetries = "FN_DS_DB_PING_MAX_RETRIES"
)
@@ -1185,6 +1187,8 @@ func (ds *SQLStore) InsertTrigger(ctx context.Context, newTrigger *models.Trigge
if err := r.Scan(new(int)); err != nil {
if err == sql.ErrNoRows {
return models.ErrAppsNotFound
} else if err != nil {
return err
}
}
@@ -1194,12 +1198,23 @@ func (ds *SQLStore) InsertTrigger(ctx context.Context, newTrigger *models.Trigge
if err := r.Scan(&app_id); err != nil {
if err == sql.ErrNoRows {
return models.ErrFnsNotFound
} else if err != nil {
return err
}
}
if app_id != trigger.AppID {
return models.ErrTriggerFnIDNotSameApp
}
query = tx.Rebind(`SELECT 1 FROM triggers WHERE app_id=? AND type=? and source=?`)
r = tx.QueryRowContext(ctx, query, trigger.AppID, trigger.Type, trigger.Source)
err := r.Scan(new(int))
if err == nil {
return models.ErrTriggerSourceExists
} else if err != sql.ErrNoRows {
return err
}
query = tx.Rebind(`INSERT INTO triggers (
id,
name,
@@ -1318,8 +1333,7 @@ func (ds *SQLStore) GetTriggerByID(ctx context.Context, triggerID string) (*mode
err := row.StructScan(&trigger)
if err == sql.ErrNoRows {
return nil, models.ErrTriggerNotFound
}
if err != nil {
} else if err != nil {
return nil, err
}
@@ -1403,10 +1417,26 @@ func (ds *SQLStore) GetTriggers(ctx context.Context, filter *models.TriggerFilte
if err == sql.ErrNoRows {
return res, nil // no error for empty list
}
return nil, err
}
return res, nil
}
func (ds *SQLStore) GetTriggerBySource(ctx context.Context, appId string, triggerType, source string) (*models.Trigger, error) {
var trigger models.Trigger
query := ds.db.Rebind(triggerIDSourceSelector)
row := ds.db.QueryRowxContext(ctx, query, appId, triggerType, source)
err := row.StructScan(&trigger)
if err == sql.ErrNoRows {
return nil, models.ErrTriggerNotFound
} else if err != nil {
return nil, err
}
return &trigger, nil
}
// Close closes the database, releasing any open resources.
func (ds *SQLStore) Close() error {
return ds.db.Close()