[Feature] Function status

This commit is contained in:
Denis Makogon
2017-06-06 14:12:50 -07:00
parent 6334f44a72
commit 3f065ce6bf
29 changed files with 1165 additions and 564 deletions

View File

@@ -12,9 +12,12 @@ import (
"net/url"
"os"
"reflect"
"time"
"github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
"github.com/go-openapi/strfmt"
"github.com/satori/go.uuid"
)
func setLogBuffer() *bytes.Buffer {
@@ -41,6 +44,50 @@ func Test(t *testing.T, ds models.Datastore) {
ctx := context.Background()
task := &models.Task{}
task.CreatedAt = strfmt.DateTime(time.Now())
task.Status = "success"
task.StartedAt = strfmt.DateTime(time.Now())
task.CompletedAt = strfmt.DateTime(time.Now())
task.AppName = testApp.Name
task.Path = testRoute.Path
t.Run("call-insert", func(t *testing.T) {
task.ID = uuid.NewV4().String()
err := ds.InsertTask(ctx, task)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test InsertTask(ctx, &task): unexpected error `%v`", err)
}
})
t.Run("call-get", func(t *testing.T) {
task.ID = uuid.NewV4().String()
ds.InsertTask(ctx, task)
newTask, err := ds.GetTask(ctx, task.ID)
if err != nil {
t.Fatalf("Test GetTask(ctx, task.ID): unexpected error `%v`", err)
}
if task.ID != newTask.ID {
t.Log(buf.String())
t.Fatalf("Test GetTask(ctx, task.ID): unexpected error `%v`", err)
}
})
t.Run("calls-get", func(t *testing.T) {
filter := &models.CallFilter{AppName: task.AppName, Path:task.Path}
task.ID = uuid.NewV4().String()
ds.InsertTask(ctx, task)
calls, err := ds.GetTasks(ctx, filter)
if err != nil {
t.Fatalf("Test GetTasks(ctx, filter): unexpected error `%v`", err)
}
if len(calls) == 0 {
t.Log(buf.String())
t.Fatalf("Test GetTasks(ctx, filter): unexpected error `%v`", err)
}
})
t.Run("apps", func(t *testing.T) {
// Testing insert app
_, err := ds.InsertApp(ctx, nil)

View File

@@ -0,0 +1,375 @@
package datastoreutil
import (
"bytes"
"database/sql"
"encoding/json"
"github.com/Sirupsen/logrus"
"fmt"
"strings"
"gitlab-odx.oracle.com/odx/functions/api/models"
)
type RowScanner interface {
Scan(dest ...interface{}) error
}
func ScanRoute(scanner RowScanner, route *models.Route) error {
var headerStr string
var configStr string
err := scanner.Scan(
&route.AppName,
&route.Path,
&route.Image,
&route.Format,
&route.MaxConcurrency,
&route.Memory,
&route.Type,
&route.Timeout,
&route.IdleTimeout,
&headerStr,
&configStr,
)
if err != nil {
return err
}
if len(headerStr) > 0 {
err = json.Unmarshal([]byte(headerStr), &route.Headers)
if err != nil {
return err
}
}
if len(configStr) > 0 {
err = json.Unmarshal([]byte(configStr), &route.Config)
if err != nil {
return err
}
}
return nil
}
func ScanApp(scanner RowScanner, app *models.App) error {
var configStr string
err := scanner.Scan(
&app.Name,
&configStr,
)
if err != nil {
return err
}
if len(configStr) > 0 {
err = json.Unmarshal([]byte(configStr), &app.Config)
if err != nil {
return err
}
}
return nil
}
func BuildFilterRouteQuery(filter *models.RouteFilter, whereStm, andStm string) (string, []interface{}) {
if filter == nil {
return "", nil
}
var b bytes.Buffer
var args []interface{}
where := func(colOp, val string) {
if val != "" {
args = append(args, val)
if len(args) == 1 {
fmt.Fprintf(&b, whereStm, colOp)
} else {
//TODO: maybe better way to detect/driver SQL dialect-specific things
if strings.Contains(whereStm, "$") {
// PgSQL specific
fmt.Fprintf(&b, andStm, colOp, len(args))
} else {
// MySQL specific
fmt.Fprintf(&b, andStm, colOp)
}
}
}
}
where("path =", filter.Path)
where("app_name =", filter.AppName)
where("image =", filter.Image)
return b.String(), args
}
func BuildFilterAppQuery(filter *models.AppFilter, whereStm string) (string, []interface{}) {
if filter == nil {
return "", nil
}
if filter.Name != "" {
return whereStm, []interface{}{filter.Name}
}
return "", nil
}
func BuildFilterCallQuery(filter *models.CallFilter, whereStm, andStm string) (string, []interface{}) {
if filter == nil {
return "", nil
}
var b bytes.Buffer
var args []interface{}
where := func(colOp, val string) {
if val != "" {
args = append(args, val)
if len(args) == 1 {
fmt.Fprintf(&b, whereStm, colOp)
} else {
fmt.Fprintf(&b, andStm, colOp)
}
}
}
where("path =", filter.Path)
where("app_name =", filter.AppName)
return b.String(), args
}
func ScanCall(scanner RowScanner, call *models.FnCall) error {
err := scanner.Scan(
&call.ID,
&call.CreatedAt,
&call.StartedAt,
&call.CompletedAt,
&call.Status,
&call.AppName,
&call.Path,
)
if err == sql.ErrNoRows {
return models.ErrCallNotFound
} else if err != nil {
return err
}
return nil
}
func SQLGetCall(db *sql.DB, callSelector, callID, whereStm string) (*models.FnCall, error) {
var call models.FnCall
row := db.QueryRow(fmt.Sprintf(whereStm, callSelector), callID)
err := ScanCall(row, &call)
if err != nil {
return nil, err
}
return &call, nil
}
func SQLGetCalls(db *sql.DB, cSelector string, filter *models.CallFilter, whereStm, andStm string) (models.FnCalls, error) {
res := models.FnCalls{}
filterQuery, args := BuildFilterCallQuery(filter, whereStm, andStm)
rows, err := db.Query(fmt.Sprintf("%s %s", cSelector, filterQuery), args...)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var call models.FnCall
err := ScanCall(rows, &call)
if err != nil {
continue
}
res = append(res, &call)
}
if err := rows.Err(); err != nil {
return nil, err
}
return res, nil
}
func SQLGetApp(db *sql.DB, queryStr string, queryArgs ...interface{}) (*models.App, error) {
row := db.QueryRow(queryStr, queryArgs...)
var resName string
var config string
err := row.Scan(&resName, &config)
if err != nil {
if err == sql.ErrNoRows {
return nil, models.ErrAppsNotFound
}
return nil, err
}
res := &models.App{
Name: resName,
}
if len(config) > 0 {
err := json.Unmarshal([]byte(config), &res.Config)
if err != nil {
return nil, err
}
}
return res, nil
}
func SQLGetApps(db *sql.DB, filter *models.AppFilter, whereStm, selectStm string) ([]*models.App, error) {
res := []*models.App{}
filterQuery, args := BuildFilterAppQuery(filter, whereStm)
rows, err := db.Query(fmt.Sprintf(selectStm, filterQuery), args...)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var app models.App
err := ScanApp(rows, &app)
if err != nil {
if err == sql.ErrNoRows {
return res, nil
}
return res, err
}
res = append(res, &app)
}
if err := rows.Err(); err != nil {
return res, err
}
return res, nil
}
func NewDatastore(dataSourceName, dialect string, tables []string) (*sql.DB, error) {
db, err := sql.Open(dialect, dataSourceName)
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
return nil, err
}
maxIdleConns := 30 // c.MaxIdleConnections
db.SetMaxIdleConns(maxIdleConns)
logrus.WithFields(logrus.Fields{"max_idle_connections": maxIdleConns}).Info(
fmt.Sprintf("%v datastore dialed", dialect))
for _, v := range tables {
_, err = db.Exec(v)
if err != nil {
return nil, err
}
}
return db, nil
}
func SQLGetRoutes(db *sql.DB, filter *models.RouteFilter, rSelect string, whereStm, andStm string) ([]*models.Route, error) {
res := []*models.Route{}
filterQuery, args := BuildFilterRouteQuery(filter, whereStm, andStm)
rows, err := db.Query(fmt.Sprintf("%s %s", rSelect, filterQuery), args...)
// todo: check for no rows so we don't respond with a sql 500 err
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var route models.Route
err := ScanRoute(rows, &route)
if err != nil {
continue
}
res = append(res, &route)
}
if err := rows.Err(); err != nil {
return nil, err
}
return res, nil
}
func SQLGetRoutesByApp(db *sql.DB, appName string, filter *models.RouteFilter, rSelect, defaultFilterQuery, whereStm, andStm string) ([]*models.Route, error) {
res := []*models.Route{}
var filterQuery string
var args []interface{}
if filter == nil {
filterQuery = defaultFilterQuery
args = []interface{}{appName}
} else {
filter.AppName = appName
filterQuery, args = BuildFilterRouteQuery(filter, whereStm, andStm)
}
rows, err := db.Query(fmt.Sprintf("%s %s", rSelect, filterQuery), args...)
// todo: check for no rows so we don't respond with a sql 500 err
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var route models.Route
err := ScanRoute(rows, &route)
if err != nil {
continue
}
res = append(res, &route)
}
if err := rows.Err(); err != nil {
return nil, err
}
return res, nil
}
func SQLGetRoute(db *sql.DB, appName, routePath, rSelectCondition, routeSelector string) (*models.Route, error) {
var route models.Route
row := db.QueryRow(fmt.Sprintf(rSelectCondition, routeSelector), appName, routePath)
err := ScanRoute(row, &route)
if err == sql.ErrNoRows {
return nil, models.ErrRoutesNotFound
} else if err != nil {
return nil, err
}
return &route, nil
}
func SQLRemoveRoute(db *sql.DB, appName, routePath, deleteStm string) error {
res, err := db.Exec(deleteStm, routePath, appName)
if err != nil {
return err
}
n, err := res.RowsAffected()
if err != nil {
return err
}
if n == 0 {
return models.ErrRoutesRemoving
}
return nil
}

View File

@@ -33,6 +33,10 @@ type Datastore interface {
InsertRoute(ctx context.Context, route *models.Route) (*models.Route, error)
UpdateRoute(ctx context.Context, route *models.Route) (*models.Route, error)
InsertTask(ctx context.Context, task *models.Task) error
GetTask(ctx context.Context, callID string) (*models.FnCall, error)
GetTasks(ctx context.Context, filter *models.CallFilter) (models.FnCalls, error)
// key will never be nil/empty
Put(ctx context.Context, key, val []byte) error
Get(ctx context.Context, key []byte) ([]byte, error)
@@ -164,4 +168,19 @@ func (v *validator) Get(ctx context.Context, key []byte) ([]byte, error) {
return nil, models.ErrDatastoreEmptyKey
}
return v.ds.Get(ctx, key)
}
}
func (v *validator) InsertTask(ctx context.Context, task *models.Task) error {
return v.ds.InsertTask(ctx, task)
}
func (v *validator) GetTask(ctx context.Context, callID string) (*models.FnCall, error) {
if callID == "" {
return nil, models.ErrDatastoreEmptyTaskID
}
return v.ds.GetTask(ctx, callID)
}
func (v *validator) GetTasks(ctx context.Context, filter *models.CallFilter) (models.FnCalls, error) {
return v.ds.GetTasks(ctx, filter)
}