From 774d53662f8bff48ee731f0c299faab6431c4b08 Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Mon, 11 Sep 2017 10:57:53 +0300 Subject: [PATCH 01/12] Making logs app-bound Partially-Closes: #302 --- api/agent/func_logger.go | 18 ++++++++++-------- .../internal/datastoreutil/metrics.go | 12 ++++++------ .../internal/datastoreutil/validator.go | 4 ++-- api/datastore/sql/sql.go | 19 ++++++++++--------- api/logs/mock.go | 6 +++--- api/logs/testing/test.go | 12 ++++++------ api/models/logs.go | 6 +++--- api/server/call_logs.go | 4 ++-- 8 files changed, 42 insertions(+), 39 deletions(-) diff --git a/api/agent/func_logger.go b/api/agent/func_logger.go index ff394d7fa..2d856c8d4 100644 --- a/api/agent/func_logger.go +++ b/api/agent/func_logger.go @@ -51,10 +51,11 @@ func NewFuncLogger(ctx context.Context, appName, path, image, reqID string, logD // we don't need to log per line to db, but we do need to limit it limitw := newLimitWriter(MB, &dbWriter{ - Buffer: dbuf, - db: logDB, - ctx: ctx, - reqID: reqID, + Buffer: dbuf, + db: logDB, + ctx: ctx, + reqID: reqID, + appName: appName, }) // TODO / NOTE: we want linew to be first because limitw may error if limit @@ -177,15 +178,16 @@ func (li *lineWriter) Close() error { type dbWriter struct { *bytes.Buffer - db models.LogStore - ctx context.Context - reqID string + db models.LogStore + ctx context.Context + reqID string + appName string } func (w *dbWriter) Close() error { span, ctx := opentracing.StartSpanFromContext(context.Background(), "agent_log_write") defer span.Finish() - return w.db.InsertLog(ctx, w.reqID, w.String()) + return w.db.InsertLog(ctx, w.appName, w.reqID, w.String()) } func (w *dbWriter) Write(b []byte) (int, error) { diff --git a/api/datastore/internal/datastoreutil/metrics.go b/api/datastore/internal/datastoreutil/metrics.go index a48e27a77..cf1c8afbf 100644 --- a/api/datastore/internal/datastoreutil/metrics.go +++ b/api/datastore/internal/datastoreutil/metrics.go @@ -100,22 +100,22 @@ func (m *metricds) GetCalls(ctx context.Context, filter *models.CallFilter) ([]* return m.ds.GetCalls(ctx, filter) } -func (m *metricds) InsertLog(ctx context.Context, callID string, callLog string) error { +func (m *metricds) InsertLog(ctx context.Context, appName, callID, callLog string) error { span, ctx := opentracing.StartSpanFromContext(ctx, "ds_insert_log") defer span.Finish() - return m.ds.InsertLog(ctx, callID, callLog) + return m.ds.InsertLog(ctx, appName, callID, callLog) } -func (m *metricds) GetLog(ctx context.Context, callID string) (*models.CallLog, error) { +func (m *metricds) GetLog(ctx context.Context, appName, callID string) (*models.CallLog, error) { span, ctx := opentracing.StartSpanFromContext(ctx, "ds_get_log") defer span.Finish() - return m.ds.GetLog(ctx, callID) + return m.ds.GetLog(ctx, appName, callID) } -func (m *metricds) DeleteLog(ctx context.Context, callID string) error { +func (m *metricds) DeleteLog(ctx context.Context, appName, callID string) error { span, ctx := opentracing.StartSpanFromContext(ctx, "ds_delete_log") defer span.Finish() - return m.ds.DeleteLog(ctx, callID) + return m.ds.DeleteLog(ctx, appName, callID) } // instant & no context ;) diff --git a/api/datastore/internal/datastoreutil/validator.go b/api/datastore/internal/datastoreutil/validator.go index 897b6f212..c08728820 100644 --- a/api/datastore/internal/datastoreutil/validator.go +++ b/api/datastore/internal/datastoreutil/validator.go @@ -138,8 +138,8 @@ func (v *validator) GetCall(ctx context.Context, appName, callID string) (*model return v.Datastore.GetCall(ctx, appName, callID) } -func (v *validator) DeleteLog(ctx context.Context, callID string) error { - return v.Datastore.DeleteLog(ctx, callID) +func (v *validator) DeleteLog(ctx context.Context, appName, callID string) error { + return v.Datastore.DeleteLog(ctx, appName, callID) } // GetDatabase returns the underlying sqlx database implementation diff --git a/api/datastore/sql/sql.go b/api/datastore/sql/sql.go index 87d3ad13a..c80320514 100644 --- a/api/datastore/sql/sql.go +++ b/api/datastore/sql/sql.go @@ -61,6 +61,7 @@ var tables = [...]string{`CREATE TABLE IF NOT EXISTS routes ( `CREATE TABLE IF NOT EXISTS logs ( id varchar(256) NOT NULL PRIMARY KEY, + app_name varchar(256) NOT NULL, log text NOT NULL );`, } @@ -589,15 +590,15 @@ func (ds *sqlStore) GetCalls(ctx context.Context, filter *models.CallFilter) ([] return res, nil } -func (ds *sqlStore) InsertLog(ctx context.Context, callID, callLog string) error { - query := ds.db.Rebind(`INSERT INTO logs (id, log) VALUES (?, ?);`) - _, err := ds.db.ExecContext(ctx, query, callID, callLog) +func (ds *sqlStore) InsertLog(ctx context.Context, appName, callID, callLog string) error { + query := ds.db.Rebind(`INSERT INTO logs (id, app_name, log) VALUES (?, ?, ?);`) + _, err := ds.db.ExecContext(ctx, query, callID, appName, callLog) return err } -func (ds *sqlStore) GetLog(ctx context.Context, callID string) (*models.CallLog, error) { - query := ds.db.Rebind(`SELECT log FROM logs WHERE id=?`) - row := ds.db.QueryRowContext(ctx, query, callID) +func (ds *sqlStore) GetLog(ctx context.Context, appName, callID string) (*models.CallLog, error) { + query := ds.db.Rebind(`SELECT log FROM logs WHERE id=? AND app_name=?`) + row := ds.db.QueryRowContext(ctx, query, callID, appName) var log string err := row.Scan(&log) @@ -614,9 +615,9 @@ func (ds *sqlStore) GetLog(ctx context.Context, callID string) (*models.CallLog, }, nil } -func (ds *sqlStore) DeleteLog(ctx context.Context, callID string) error { - query := ds.db.Rebind(`DELETE FROM logs WHERE id=?`) - _, err := ds.db.ExecContext(ctx, query, callID) +func (ds *sqlStore) DeleteLog(ctx context.Context, appName, callID string) error { + query := ds.db.Rebind(`DELETE FROM logs WHERE id=? AND app_name=?`) + _, err := ds.db.ExecContext(ctx, query, callID, appName) return err } diff --git a/api/logs/mock.go b/api/logs/mock.go index 894d7ad7b..0a1f298c4 100644 --- a/api/logs/mock.go +++ b/api/logs/mock.go @@ -27,12 +27,12 @@ func (m *mock) SetDatastore(ctx context.Context, ds models.Datastore) { m.ds = ds } -func (m *mock) InsertLog(ctx context.Context, callID string, callLog string) error { +func (m *mock) InsertLog(ctx context.Context, appName, callID, callLog string) error { m.Logs[callID] = &models.CallLog{CallID: callID, Log: callLog} return nil } -func (m *mock) GetLog(ctx context.Context, callID string) (*models.CallLog, error) { +func (m *mock) GetLog(ctx context.Context, appName, callID string) (*models.CallLog, error) { logEntry := m.Logs[callID] if logEntry == nil { return nil, errors.New("Call log not found") @@ -41,7 +41,7 @@ func (m *mock) GetLog(ctx context.Context, callID string) (*models.CallLog, erro return m.Logs[callID], nil } -func (m *mock) DeleteLog(ctx context.Context, callID string) error { +func (m *mock) DeleteLog(ctx context.Context, appName, callID string) error { delete(m.Logs, callID) return nil } diff --git a/api/logs/testing/test.go b/api/logs/testing/test.go index 8055c84ac..482b7affa 100644 --- a/api/logs/testing/test.go +++ b/api/logs/testing/test.go @@ -44,7 +44,7 @@ func Test(t *testing.T, fnl models.LogStore, ds models.Datastore) { if err != nil { t.Fatalf("Test InsertCall(ctx, &call): unexpected error `%v`", err) } - err = fnl.InsertLog(ctx, call.ID, "test") + err = fnl.InsertLog(ctx, call.AppName, call.ID, "test") if err != nil { t.Fatalf("Test InsertLog(ctx, call.ID, logText): unexpected error during inserting log `%v`", err) } @@ -56,11 +56,11 @@ func Test(t *testing.T, fnl models.LogStore, ds models.Datastore) { if err != nil { t.Fatalf("Test InsertCall(ctx, &call): unexpected error `%v`", err) } - err = fnl.InsertLog(ctx, call.ID, logText) + err = fnl.InsertLog(ctx, call.AppName, call.ID, logText) if err != nil { t.Fatalf("Test InsertLog(ctx, call.ID, logText): unexpected error during inserting log `%v`", err) } - logEntry, err := fnl.GetLog(ctx, call.ID) + logEntry, err := fnl.GetLog(ctx, call.AppName, call.ID) if !strings.Contains(logEntry.Log, logText) { t.Fatalf("Test GetLog(ctx, call.ID, logText): unexpected error, log mismatch. "+ "Expected: `%v`. Got `%v`.", logText, logEntry.Log) @@ -73,16 +73,16 @@ func Test(t *testing.T, fnl models.LogStore, ds models.Datastore) { if err != nil { t.Fatalf("Test InsertCall(ctx, &call): unexpected error `%v`", err) } - err = fnl.InsertLog(ctx, call.ID, logText) + err = fnl.InsertLog(ctx, call.AppName, call.ID, logText) if err != nil { t.Fatalf("Test InsertLog(ctx, call.ID, logText): unexpected error during inserting log `%v`", err) } - logEntry, err := fnl.GetLog(ctx, call.ID) + logEntry, err := fnl.GetLog(ctx, call.AppName, call.ID) if !strings.Contains(logEntry.Log, logText) { t.Fatalf("Test GetLog(ctx, call.ID, logText): unexpected error, log mismatch. "+ "Expected: `%v`. Got `%v`.", logText, logEntry.Log) } - err = fnl.DeleteLog(ctx, call.ID) + err = fnl.DeleteLog(ctx, call.AppName, call.ID) if err != nil { t.Fatalf("Test DeleteLog(ctx, call.ID): unexpected error during deleting log `%v`", err) } diff --git a/api/models/logs.go b/api/models/logs.go index fe0fb5634..a4cc7da88 100644 --- a/api/models/logs.go +++ b/api/models/logs.go @@ -11,13 +11,13 @@ type LogStore interface { // InsertLog will insert the log at callID, overwriting if it previously // existed. - InsertLog(ctx context.Context, callID string, callLog string) error + InsertLog(ctx context.Context, appName, callID string, callLog string) error // GetLog will return the log at callID, an error will be returned if the log // cannot be found. - GetLog(ctx context.Context, callID string) (*CallLog, error) + GetLog(ctx context.Context, appName, callID string) (*CallLog, error) // DeleteLog will remove the log at callID, it will not return an error if // the log does not exist before removal. - DeleteLog(ctx context.Context, callID string) error + DeleteLog(ctx context.Context, appName, callID string) error } diff --git a/api/server/call_logs.go b/api/server/call_logs.go index 8ee04ba7a..0f9ef83ae 100644 --- a/api/server/call_logs.go +++ b/api/server/call_logs.go @@ -18,7 +18,7 @@ func (s *Server) handleCallLogGet(c *gin.Context) { return } - callObj, err := s.LogDB.GetLog(ctx, callID) + callObj, err := s.LogDB.GetLog(ctx, appName, callID) if err != nil { handleErrorResponse(c, err) return @@ -37,7 +37,7 @@ func (s *Server) handleCallLogDelete(c *gin.Context) { handleErrorResponse(c, err) return } - err = s.LogDB.DeleteLog(ctx, callID) + err = s.LogDB.DeleteLog(ctx, appName, callID) if err != nil { handleErrorResponse(c, err) return From 3e190342fb77d58d2e95245ea8ec175686f34ece Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Mon, 11 Sep 2017 11:41:03 +0300 Subject: [PATCH 02/12] Implementing batch deletes for calls, logs and routes Partially-Closes: #302 --- .../internal/datastoreutil/metrics.go | 18 +++++++++++++++ .../internal/datastoreutil/validator.go | 12 ++++++++++ api/datastore/mock.go | 22 +++++++++++++++++++ api/datastore/sql/sql.go | 18 +++++++++++++++ api/logs/mock.go | 9 ++++++++ api/models/call.go | 5 +++-- api/models/datastore.go | 2 ++ api/models/logs.go | 2 ++ 8 files changed, 86 insertions(+), 2 deletions(-) diff --git a/api/datastore/internal/datastoreutil/metrics.go b/api/datastore/internal/datastoreutil/metrics.go index cf1c8afbf..796e4dd02 100644 --- a/api/datastore/internal/datastoreutil/metrics.go +++ b/api/datastore/internal/datastoreutil/metrics.go @@ -118,5 +118,23 @@ func (m *metricds) DeleteLog(ctx context.Context, appName, callID string) error return m.ds.DeleteLog(ctx, appName, callID) } +func (m *metricds) BatchDeleteLogs(ctx context.Context, appName string) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "ds_batch_delete_logs") + defer span.Finish() + return m.ds.BatchDeleteLogs(ctx, appName) +} + +func (m *metricds) BatchDeleteCalls(ctx context.Context, appName string) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "ds_batch_delete_calls") + defer span.Finish() + return m.ds.BatchDeleteCalls(ctx, appName) +} + +func (m *metricds) BatchDeleteRoutes(ctx context.Context, appName string) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "ds_batch_delete_routes") + defer span.Finish() + return m.ds.BatchDeleteRoutes(ctx, appName) +} + // instant & no context ;) func (m *metricds) GetDatabase() *sqlx.DB { return m.ds.GetDatabase() } diff --git a/api/datastore/internal/datastoreutil/validator.go b/api/datastore/internal/datastoreutil/validator.go index c08728820..a5bbec3e0 100644 --- a/api/datastore/internal/datastoreutil/validator.go +++ b/api/datastore/internal/datastoreutil/validator.go @@ -142,6 +142,18 @@ func (v *validator) DeleteLog(ctx context.Context, appName, callID string) error return v.Datastore.DeleteLog(ctx, appName, callID) } +func (v *validator) BatchDeleteLogs(ctx context.Context, appName string) error { + return v.Datastore.BatchDeleteLogs(ctx, appName) +} + +func (v *validator) BatchDeleteCalls(ctx context.Context, appName string) error { + return v.Datastore.BatchDeleteCalls(ctx, appName) +} + +func (v *validator) BatchDeleteRoutes(ctx context.Context, appName string) error { + return v.Datastore.BatchDeleteRoutes(ctx, appName) +} + // GetDatabase returns the underlying sqlx database implementation func (v *validator) GetDatabase() *sqlx.DB { return v.Datastore.GetDatabase() diff --git a/api/datastore/mock.go b/api/datastore/mock.go index ef5403832..31b4e137f 100644 --- a/api/datastore/mock.go +++ b/api/datastore/mock.go @@ -157,6 +157,28 @@ func (m *mock) GetCalls(ctx context.Context, filter *models.CallFilter) ([]*mode return m.Calls, nil } +func (m *mock) BatchDeleteCalls(ctx context.Context, appName string) error { + newCalls := []*models.Call{} + for _, c := range m.Calls { + if c.AppName != appName { + newCalls = append(newCalls, c) + } + } + m.Calls = newCalls + return nil +} + +func (m *mock) BatchDeleteRoutes(ctx context.Context, appName string) error { + newRoutes := []*models.Route{} + for _, c := range m.Routes { + if c.AppName != appName { + newRoutes = append(newRoutes, c) + } + } + m.Routes = newRoutes + return nil +} + // GetDatabase returns nil here since shouldn't really be used func (m *mock) GetDatabase() *sqlx.DB { return nil diff --git a/api/datastore/sql/sql.go b/api/datastore/sql/sql.go index c80320514..c2e61325f 100644 --- a/api/datastore/sql/sql.go +++ b/api/datastore/sql/sql.go @@ -621,6 +621,24 @@ func (ds *sqlStore) DeleteLog(ctx context.Context, appName, callID string) error return err } +func (ds *sqlStore) BatchDeleteLogs(ctx context.Context, appName string) error { + query := ds.db.Rebind(`DELETE FROM logs WHERE app_name=?`) + _, err := ds.db.ExecContext(ctx, query, appName) + return err +} + +func (ds *sqlStore) BatchDeleteCalls(ctx context.Context, appName string) error { + query := ds.db.Rebind(`DELETE FROM calls WHERE app_name=?`) + _, err := ds.db.ExecContext(ctx, query, appName) + return err +} + +func (ds *sqlStore) BatchDeleteRoutes(ctx context.Context, appName string) error { + query := ds.db.Rebind(`DELETE FROM routes WHERE app_name=?`) + _, err := ds.db.ExecContext(ctx, query, appName) + return err +} + // TODO scrap for sqlx scanx ?? some things aren't perfect (e.g. config is a json string) type RowScanner interface { Scan(dest ...interface{}) error diff --git a/api/logs/mock.go b/api/logs/mock.go index 0a1f298c4..cda74c9c4 100644 --- a/api/logs/mock.go +++ b/api/logs/mock.go @@ -45,3 +45,12 @@ func (m *mock) DeleteLog(ctx context.Context, appName, callID string) error { delete(m.Logs, callID) return nil } + +func (m *mock) BatchDeleteLogs(ctx context.Context, appName string) error { + for _, log := range m.Logs { + if log.AppName == appName { + m.DeleteLog(ctx, appName, log.CallID) + } + } + return nil +} diff --git a/api/models/call.go b/api/models/call.go index eef0bd3be..8a33cb3d6 100644 --- a/api/models/call.go +++ b/api/models/call.go @@ -23,8 +23,9 @@ const ( var possibleStatuses = [...]string{"delayed", "queued", "running", "success", "error", "cancelled"} type CallLog struct { - CallID string `json:"call_id"` - Log string `json:"log"` + CallID string `json:"call_id"` + Log string `json:"log"` + AppName string `json:"app_name"` } // Call is a representation of a specific invocation of a route. diff --git a/api/models/datastore.go b/api/models/datastore.go index b4e1ed798..3268619b3 100644 --- a/api/models/datastore.go +++ b/api/models/datastore.go @@ -68,6 +68,8 @@ type Datastore interface { // calls exist, an empty list and a nil error are returned. GetCalls(ctx context.Context, filter *CallFilter) ([]*Call, error) + BatchDeleteCalls(ctx context.Context, appName string) error + BatchDeleteRoutes(ctx context.Context, appName string) error // Implement LogStore methods for convenience LogStore diff --git a/api/models/logs.go b/api/models/logs.go index a4cc7da88..0ab0268db 100644 --- a/api/models/logs.go +++ b/api/models/logs.go @@ -20,4 +20,6 @@ type LogStore interface { // DeleteLog will remove the log at callID, it will not return an error if // the log does not exist before removal. DeleteLog(ctx context.Context, appName, callID string) error + + BatchDeleteLogs(ctx context.Context, appName string) error } From 78f2d51bfadedb6688672a633c083d8b4cb002a2 Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Mon, 11 Sep 2017 12:25:32 +0300 Subject: [PATCH 03/12] Implementing force delete for apps Deployment-Impact: DB schema changed API Impact: HTTP DELETE /apps/{app} accepts query parameters: /apps/{app}?force=True Closes: #302 --- api/datastore/sql/sql.go | 5 +++-- api/server/apps_delete.go | 15 +++++++++++---- docs/swagger.yml | 12 +++++++++--- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/api/datastore/sql/sql.go b/api/datastore/sql/sql.go index c2e61325f..a7ba76ce8 100644 --- a/api/datastore/sql/sql.go +++ b/api/datastore/sql/sql.go @@ -610,8 +610,9 @@ func (ds *sqlStore) GetLog(ctx context.Context, appName, callID string) (*models } return &models.CallLog{ - CallID: callID, - Log: log, + CallID: callID, + Log: log, + AppName: appName, }, nil } diff --git a/api/server/apps_delete.go b/api/server/apps_delete.go index 708a4dc65..a572ee5fa 100644 --- a/api/server/apps_delete.go +++ b/api/server/apps_delete.go @@ -7,6 +7,7 @@ import ( "github.com/fnproject/fn/api/common" "github.com/fnproject/fn/api/models" "github.com/gin-gonic/gin" + "strconv" ) func (s *Server) handleAppDelete(c *gin.Context) { @@ -21,10 +22,16 @@ func (s *Server) handleAppDelete(c *gin.Context) { handleErrorResponse(c, err) return } - //TODO allow this? #528 - if len(routes) > 0 { - handleErrorResponse(c, models.ErrDeleteAppsWithRoutes) - return + forceDelete, _ := strconv.ParseBool(c.Query("force")) + if !forceDelete { + if len(routes) > 0 { + handleErrorResponse(c, models.ErrDeleteAppsWithRoutes) + return + } + } else { + s.Datastore.BatchDeleteLogs(ctx, app.Name) + s.Datastore.BatchDeleteCalls(ctx, app.Name) + s.Datastore.BatchDeleteRoutes(ctx, app.Name) } err = s.FireBeforeAppDelete(ctx, app) diff --git a/docs/swagger.yml b/docs/swagger.yml index d91e8e53a..7b149a4c7 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -73,11 +73,17 @@ paths: tags: - Apps parameters: - - name: app - in: path + - in: path + name: app description: Name of the app. required: true - type: string + schema: + type: string + - in: query + name: force + schema: + type: bool + description: Query parameter that enables force delete for app responses: 200: description: Apps successfully deleted. From 6b7accd3c6c542ab24c98136a849070a58e3f5e2 Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Mon, 11 Sep 2017 23:15:01 +0300 Subject: [PATCH 04/12] Simplifying app delete per review comments --- .../internal/datastoreutil/metrics.go | 18 ------------ .../internal/datastoreutil/validator.go | 12 -------- api/datastore/sql/sql.go | 2 ++ api/models/datastore.go | 3 -- api/models/logs.go | 2 -- api/server/apps_delete.go | 28 +------------------ 6 files changed, 3 insertions(+), 62 deletions(-) diff --git a/api/datastore/internal/datastoreutil/metrics.go b/api/datastore/internal/datastoreutil/metrics.go index 796e4dd02..cf1c8afbf 100644 --- a/api/datastore/internal/datastoreutil/metrics.go +++ b/api/datastore/internal/datastoreutil/metrics.go @@ -118,23 +118,5 @@ func (m *metricds) DeleteLog(ctx context.Context, appName, callID string) error return m.ds.DeleteLog(ctx, appName, callID) } -func (m *metricds) BatchDeleteLogs(ctx context.Context, appName string) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "ds_batch_delete_logs") - defer span.Finish() - return m.ds.BatchDeleteLogs(ctx, appName) -} - -func (m *metricds) BatchDeleteCalls(ctx context.Context, appName string) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "ds_batch_delete_calls") - defer span.Finish() - return m.ds.BatchDeleteCalls(ctx, appName) -} - -func (m *metricds) BatchDeleteRoutes(ctx context.Context, appName string) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "ds_batch_delete_routes") - defer span.Finish() - return m.ds.BatchDeleteRoutes(ctx, appName) -} - // instant & no context ;) func (m *metricds) GetDatabase() *sqlx.DB { return m.ds.GetDatabase() } diff --git a/api/datastore/internal/datastoreutil/validator.go b/api/datastore/internal/datastoreutil/validator.go index a5bbec3e0..c08728820 100644 --- a/api/datastore/internal/datastoreutil/validator.go +++ b/api/datastore/internal/datastoreutil/validator.go @@ -142,18 +142,6 @@ func (v *validator) DeleteLog(ctx context.Context, appName, callID string) error return v.Datastore.DeleteLog(ctx, appName, callID) } -func (v *validator) BatchDeleteLogs(ctx context.Context, appName string) error { - return v.Datastore.BatchDeleteLogs(ctx, appName) -} - -func (v *validator) BatchDeleteCalls(ctx context.Context, appName string) error { - return v.Datastore.BatchDeleteCalls(ctx, appName) -} - -func (v *validator) BatchDeleteRoutes(ctx context.Context, appName string) error { - return v.Datastore.BatchDeleteRoutes(ctx, appName) -} - // GetDatabase returns the underlying sqlx database implementation func (v *validator) GetDatabase() *sqlx.DB { return v.Datastore.GetDatabase() diff --git a/api/datastore/sql/sql.go b/api/datastore/sql/sql.go index a7ba76ce8..77945fbbe 100644 --- a/api/datastore/sql/sql.go +++ b/api/datastore/sql/sql.go @@ -221,6 +221,8 @@ func (ds *sqlStore) UpdateApp(ctx context.Context, newapp *models.App) (*models. } func (ds *sqlStore) RemoveApp(ctx context.Context, appName string) error { + ds.db.ExecContext(ctx, ds.db.Rebind( + `DELETE FROM routes, calls, logs WHERE app_name=?`), appName) query := ds.db.Rebind(`DELETE FROM apps WHERE name = ?`) _, err := ds.db.ExecContext(ctx, query, appName) return err diff --git a/api/models/datastore.go b/api/models/datastore.go index 3268619b3..b77ba6b48 100644 --- a/api/models/datastore.go +++ b/api/models/datastore.go @@ -28,7 +28,6 @@ type Datastore interface { // RemoveApp removes the App named appName. Returns ErrDatastoreEmptyAppName if appName is empty. // Returns ErrAppsNotFound if an App is not found. - // TODO remove routes automatically? #528 RemoveApp(ctx context.Context, appName string) error // GetRoute looks up a matching Route for appName and the literal request route routePath. @@ -68,8 +67,6 @@ type Datastore interface { // calls exist, an empty list and a nil error are returned. GetCalls(ctx context.Context, filter *CallFilter) ([]*Call, error) - BatchDeleteCalls(ctx context.Context, appName string) error - BatchDeleteRoutes(ctx context.Context, appName string) error // Implement LogStore methods for convenience LogStore diff --git a/api/models/logs.go b/api/models/logs.go index 0ab0268db..a4cc7da88 100644 --- a/api/models/logs.go +++ b/api/models/logs.go @@ -20,6 +20,4 @@ type LogStore interface { // DeleteLog will remove the log at callID, it will not return an error if // the log does not exist before removal. DeleteLog(ctx context.Context, appName, callID string) error - - BatchDeleteLogs(ctx context.Context, appName string) error } diff --git a/api/server/apps_delete.go b/api/server/apps_delete.go index a572ee5fa..bdcbba751 100644 --- a/api/server/apps_delete.go +++ b/api/server/apps_delete.go @@ -7,7 +7,6 @@ import ( "github.com/fnproject/fn/api/common" "github.com/fnproject/fn/api/models" "github.com/gin-gonic/gin" - "strconv" ) func (s *Server) handleAppDelete(c *gin.Context) { @@ -16,32 +15,7 @@ func (s *Server) handleAppDelete(c *gin.Context) { app := &models.App{Name: c.MustGet(api.AppName).(string)} - routes, err := s.Datastore.GetRoutesByApp(ctx, app.Name, &models.RouteFilter{}) - if err != nil { - log.WithError(err).Error("error getting route in app delete") - handleErrorResponse(c, err) - return - } - forceDelete, _ := strconv.ParseBool(c.Query("force")) - if !forceDelete { - if len(routes) > 0 { - handleErrorResponse(c, models.ErrDeleteAppsWithRoutes) - return - } - } else { - s.Datastore.BatchDeleteLogs(ctx, app.Name) - s.Datastore.BatchDeleteCalls(ctx, app.Name) - s.Datastore.BatchDeleteRoutes(ctx, app.Name) - } - - err = s.FireBeforeAppDelete(ctx, app) - if err != nil { - log.WithError(err).Error("error firing before app delete") - handleErrorResponse(c, err) - return - } - - app, err = s.Datastore.GetApp(ctx, app.Name) + app, err := s.Datastore.GetApp(ctx, app.Name) if err != nil { handleErrorResponse(c, err) return From 24a06cf11167d6dfea75d6777f712833afde6bad Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Mon, 11 Sep 2017 23:23:18 +0300 Subject: [PATCH 05/12] Cleaning API tests from redundant route delete on each test --- test/fn-api-tests/calls_test.go | 3 --- test/fn-api-tests/exec_test.go | 8 -------- test/fn-api-tests/routes_test.go | 11 ----------- 3 files changed, 22 deletions(-) diff --git a/test/fn-api-tests/calls_test.go b/test/fn-api-tests/calls_test.go index 339c075d7..f0e57ffef 100644 --- a/test/fn-api-tests/calls_test.go +++ b/test/fn-api-tests/calls_test.go @@ -62,7 +62,6 @@ func TestCalls(t *testing.T) { t.Error("Must fail because `dummy` call does not exist.") } - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -97,7 +96,6 @@ func TestCalls(t *testing.T) { t.Errorf("Unexpected error occurred: %v.", msg) } } - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -135,7 +133,6 @@ func TestCalls(t *testing.T) { t.Errorf("Call path mismatch.\n\tExpected: %v\n\tActual: %v", c.Path, s.RoutePath) } } - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) diff --git a/test/fn-api-tests/exec_test.go b/test/fn-api-tests/exec_test.go index 8a8c76263..a7b1fa664 100644 --- a/test/fn-api-tests/exec_test.go +++ b/test/fn-api-tests/exec_test.go @@ -75,7 +75,6 @@ func TestRouteExecutions(t *testing.T) { if !strings.Contains(expectedOutput, output.String()) { t.Errorf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String()) } - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -105,7 +104,6 @@ func TestRouteExecutions(t *testing.T) { if !strings.Contains(expectedOutput, output.String()) { t.Errorf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String()) } - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -132,7 +130,6 @@ func TestRouteExecutions(t *testing.T) { CheckRouteResponseError(t, err) CallAsync(t, u, &bytes.Buffer{}) - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -188,7 +185,6 @@ func TestRouteExecutions(t *testing.T) { t.Errorf("Call object status mismatch.\n\tExpected: %v\n\tActual:%v", "success", callObject.Status) } - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -237,7 +233,6 @@ func TestRouteExecutions(t *testing.T) { "output", "callObj.Payload.Call.Status") } - DeleteRoute(t, s.Context, s.Client, s.AppName, routePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -283,7 +278,6 @@ func TestRouteExecutions(t *testing.T) { "string, but got: %v", logObj.Payload.Log.Log) } - DeleteRoute(t, s.Context, s.Client, s.AppName, routePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -354,7 +348,6 @@ func TestRouteExecutions(t *testing.T) { t.Errorf("Unexpected error: %s", err) } - DeleteRoute(t, s.Context, s.Client, s.AppName, routePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -399,7 +392,6 @@ func TestRouteExecutions(t *testing.T) { t.Errorf("Log entry suppose to be truncated up to expected size %v, got %v", size/1024, len(logObj.Payload.Log.Log)) } - DeleteRoute(t, s.Context, s.Client, s.AppName, routePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) diff --git a/test/fn-api-tests/routes_test.go b/test/fn-api-tests/routes_test.go index 0e72ff150..ad82f376f 100644 --- a/test/fn-api-tests/routes_test.go +++ b/test/fn-api-tests/routes_test.go @@ -29,7 +29,6 @@ func TestRoutes(t *testing.T) { CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, s.RouteHeaders) - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -42,7 +41,6 @@ func TestRoutes(t *testing.T) { if !assertContainsRoute(ListRoutes(t, s.Context, s.Client, s.AppName), s.RoutePath) { t.Errorf("Unable to find corresponding route `%v` in list", s.RoutePath) } - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -58,7 +56,6 @@ func TestRoutes(t *testing.T) { t.Errorf("Unable to find corresponding route `%v` in list", s.RoutePath) } - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -78,7 +75,6 @@ func TestRoutes(t *testing.T) { CheckRouteResponseError(t, err) assertRouteFields(t, routeResp.Payload.Route, s.RoutePath, s.Image, newRouteType, s.Format) - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -102,7 +98,6 @@ func TestRoutes(t *testing.T) { CheckRouteResponseError(t, err) assertRouteFields(t, routeResp.Payload.Route, s.RoutePath, s.Image, s.RouteType, s.Format) - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -122,7 +117,6 @@ func TestRoutes(t *testing.T) { t.Errorf("Route path suppose to be immutable, but it's not.") } - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -138,7 +132,6 @@ func TestRoutes(t *testing.T) { t.Errorf("Route duplicate error should appear, but it didn't") } - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -171,7 +164,6 @@ func TestRoutes(t *testing.T) { DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, s.RouteHeaders) GetApp(t, s.Context, s.Client, s.AppName) GetRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -181,7 +173,6 @@ func TestRoutes(t *testing.T) { DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, s.RouteHeaders) GetApp(t, s.Context, s.Client, s.AppName) GetRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) @@ -199,8 +190,6 @@ func TestRoutes(t *testing.T) { s.Format, s.RouteConfig, s.RouteHeaders) assertRouteFields(t, updatedRoute, s.RoutePath, s.Image, newRouteType, s.Format) - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) DeleteApp(t, s.Context, s.Client, s.AppName) }) - } From 8c9e7443cbd7585c433bdbd9bd333b5242de3a4a Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Mon, 11 Sep 2017 23:28:51 +0300 Subject: [PATCH 06/12] Removing no longer valid tests --- api/server/server_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/server/server_test.go b/api/server/server_test.go index 9d4687703..ae3043597 100644 --- a/api/server/server_test.go +++ b/api/server/server_test.go @@ -121,7 +121,6 @@ func TestFullStack(t *testing.T) { {"execute myroute2", "POST", "/r/myapp/myroute2", `{ "name": "Teste" }`, http.StatusInternalServerError, 2}, {"get myroute2", "GET", "/v1/apps/myapp/routes/myroute2", ``, http.StatusOK, 2}, {"delete myroute", "DELETE", "/v1/apps/myapp/routes/myroute", ``, http.StatusOK, 1}, - {"delete app (fail)", "DELETE", "/v1/apps/myapp", ``, http.StatusConflict, 1}, {"delete myroute2", "DELETE", "/v1/apps/myapp/routes/myroute2", ``, http.StatusOK, 0}, {"delete app (success)", "DELETE", "/v1/apps/myapp", ``, http.StatusOK, 0}, {"get deleted app", "GET", "/v1/apps/myapp", ``, http.StatusNotFound, 0}, From 33a8b87dc0650b1a46e5d7e784dadc7deeee048a Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Mon, 11 Sep 2017 23:57:47 +0300 Subject: [PATCH 07/12] Reverting swagger doc --- docs/swagger.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 7b149a4c7..d91e8e53a 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -73,17 +73,11 @@ paths: tags: - Apps parameters: - - in: path - name: app + - name: app + in: path description: Name of the app. required: true - schema: - type: string - - in: query - name: force - schema: - type: bool - description: Query parameter that enables force delete for app + type: string responses: 200: description: Apps successfully deleted. From 4052a7d4280defdb32a4d85885a85c6d42671de7 Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Tue, 12 Sep 2017 20:07:40 +0300 Subject: [PATCH 08/12] Addressing review comments --- api/datastore/mock.go | 6 ++++-- api/datastore/sql/sql.go | 30 +++++++++--------------------- api/logs/mock.go | 9 --------- api/server/apps_delete.go | 2 ++ 4 files changed, 15 insertions(+), 32 deletions(-) diff --git a/api/datastore/mock.go b/api/datastore/mock.go index 31b4e137f..2aa6f9b33 100644 --- a/api/datastore/mock.go +++ b/api/datastore/mock.go @@ -60,6 +60,8 @@ func (m *mock) UpdateApp(ctx context.Context, app *models.App) (*models.App, err } func (m *mock) RemoveApp(ctx context.Context, appName string) error { + m.batchDeleteCalls(ctx, appName) + m.batchDeleteRoutes(ctx, appName) for i, a := range m.Apps { if a.Name == appName { m.Apps = append(m.Apps[:i], m.Apps[i+1:]...) @@ -157,7 +159,7 @@ func (m *mock) GetCalls(ctx context.Context, filter *models.CallFilter) ([]*mode return m.Calls, nil } -func (m *mock) BatchDeleteCalls(ctx context.Context, appName string) error { +func (m *mock) batchDeleteCalls(ctx context.Context, appName string) error { newCalls := []*models.Call{} for _, c := range m.Calls { if c.AppName != appName { @@ -168,7 +170,7 @@ func (m *mock) BatchDeleteCalls(ctx context.Context, appName string) error { return nil } -func (m *mock) BatchDeleteRoutes(ctx context.Context, appName string) error { +func (m *mock) batchDeleteRoutes(ctx context.Context, appName string) error { newRoutes := []*models.Route{} for _, c := range m.Routes { if c.AppName != appName { diff --git a/api/datastore/sql/sql.go b/api/datastore/sql/sql.go index 77945fbbe..35cc94bd0 100644 --- a/api/datastore/sql/sql.go +++ b/api/datastore/sql/sql.go @@ -221,10 +221,16 @@ func (ds *sqlStore) UpdateApp(ctx context.Context, newapp *models.App) (*models. } func (ds *sqlStore) RemoveApp(ctx context.Context, appName string) error { - ds.db.ExecContext(ctx, ds.db.Rebind( - `DELETE FROM routes, calls, logs WHERE app_name=?`), appName) query := ds.db.Rebind(`DELETE FROM apps WHERE name = ?`) - _, err := ds.db.ExecContext(ctx, query, appName) + res, err := ds.db.ExecContext(ctx, query, appName) + if _, err := res.RowsAffected(); err != nil { + return models.ErrAppsNotFound + } + _, err = ds.db.ExecContext(ctx, ds.db.Rebind( + `DELETE FROM routes, calls, logs WHERE app_name=?`), appName) + if err != nil { + return err + } return err } @@ -624,24 +630,6 @@ func (ds *sqlStore) DeleteLog(ctx context.Context, appName, callID string) error return err } -func (ds *sqlStore) BatchDeleteLogs(ctx context.Context, appName string) error { - query := ds.db.Rebind(`DELETE FROM logs WHERE app_name=?`) - _, err := ds.db.ExecContext(ctx, query, appName) - return err -} - -func (ds *sqlStore) BatchDeleteCalls(ctx context.Context, appName string) error { - query := ds.db.Rebind(`DELETE FROM calls WHERE app_name=?`) - _, err := ds.db.ExecContext(ctx, query, appName) - return err -} - -func (ds *sqlStore) BatchDeleteRoutes(ctx context.Context, appName string) error { - query := ds.db.Rebind(`DELETE FROM routes WHERE app_name=?`) - _, err := ds.db.ExecContext(ctx, query, appName) - return err -} - // TODO scrap for sqlx scanx ?? some things aren't perfect (e.g. config is a json string) type RowScanner interface { Scan(dest ...interface{}) error diff --git a/api/logs/mock.go b/api/logs/mock.go index cda74c9c4..0a1f298c4 100644 --- a/api/logs/mock.go +++ b/api/logs/mock.go @@ -45,12 +45,3 @@ func (m *mock) DeleteLog(ctx context.Context, appName, callID string) error { delete(m.Logs, callID) return nil } - -func (m *mock) BatchDeleteLogs(ctx context.Context, appName string) error { - for _, log := range m.Logs { - if log.AppName == appName { - m.DeleteLog(ctx, appName, log.CallID) - } - } - return nil -} diff --git a/api/server/apps_delete.go b/api/server/apps_delete.go index bdcbba751..abb8687b5 100644 --- a/api/server/apps_delete.go +++ b/api/server/apps_delete.go @@ -21,6 +21,8 @@ func (s *Server) handleAppDelete(c *gin.Context) { return } + err = s.FireBeforeAppDelete(ctx, app) + err = s.Datastore.RemoveApp(ctx, app.Name) if err != nil { handleErrorResponse(c, err) From bdb7e7fd7b41d3ecdfbb18688f71bdcdb56c0e14 Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Tue, 12 Sep 2017 20:09:22 +0300 Subject: [PATCH 09/12] Addressing more comments --- api/server/apps_delete.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/api/server/apps_delete.go b/api/server/apps_delete.go index abb8687b5..56c963caa 100644 --- a/api/server/apps_delete.go +++ b/api/server/apps_delete.go @@ -15,13 +15,7 @@ func (s *Server) handleAppDelete(c *gin.Context) { app := &models.App{Name: c.MustGet(api.AppName).(string)} - app, err := s.Datastore.GetApp(ctx, app.Name) - if err != nil { - handleErrorResponse(c, err) - return - } - - err = s.FireBeforeAppDelete(ctx, app) + err := s.FireBeforeAppDelete(ctx, app) err = s.Datastore.RemoveApp(ctx, app.Name) if err != nil { From 0b47c8c40c0130a6a08405c71f21ebdf43db96ec Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Tue, 12 Sep 2017 20:42:52 +0300 Subject: [PATCH 10/12] Fix sql statements --- api/datastore/sql/sql.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/datastore/sql/sql.go b/api/datastore/sql/sql.go index 35cc94bd0..52b488d65 100644 --- a/api/datastore/sql/sql.go +++ b/api/datastore/sql/sql.go @@ -227,7 +227,17 @@ func (ds *sqlStore) RemoveApp(ctx context.Context, appName string) error { return models.ErrAppsNotFound } _, err = ds.db.ExecContext(ctx, ds.db.Rebind( - `DELETE FROM routes, calls, logs WHERE app_name=?`), appName) + `DELETE FROM logs WHERE app_name=?`), appName) + if err != nil { + return err + } + _, err = ds.db.ExecContext(ctx, ds.db.Rebind( + `DELETE FROM calls WHERE app_name=?`), appName) + if err != nil { + return err + } + _, err = ds.db.ExecContext(ctx, ds.db.Rebind( + `DELETE FROM routes WHERE app_name=?`), appName) if err != nil { return err } From 93d4a5730bfd6dba41874a73eb38a40fd6e67b0f Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Tue, 12 Sep 2017 23:30:31 +0300 Subject: [PATCH 11/12] Running deletes in single transaction --- api/datastore/sql/sql.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/api/datastore/sql/sql.go b/api/datastore/sql/sql.go index 52b488d65..2a7981ef4 100644 --- a/api/datastore/sql/sql.go +++ b/api/datastore/sql/sql.go @@ -227,21 +227,13 @@ func (ds *sqlStore) RemoveApp(ctx context.Context, appName string) error { return models.ErrAppsNotFound } _, err = ds.db.ExecContext(ctx, ds.db.Rebind( - `DELETE FROM logs WHERE app_name=?`), appName) + `DELETE FROM logs WHERE app_name=?; + DELETE FROM calls WHERE app_name=?; + DELETE FROM routes WHERE app_name=?;`), appName, appName, appName) if err != nil { return err } - _, err = ds.db.ExecContext(ctx, ds.db.Rebind( - `DELETE FROM calls WHERE app_name=?`), appName) - if err != nil { - return err - } - _, err = ds.db.ExecContext(ctx, ds.db.Rebind( - `DELETE FROM routes WHERE app_name=?`), appName) - if err != nil { - return err - } - return err + return nil } func (ds *sqlStore) GetApp(ctx context.Context, name string) (*models.App, error) { From b2ca5ebc6438b3924f4ece85f219bda1fea1e65a Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Tue, 12 Sep 2017 23:39:18 +0300 Subject: [PATCH 12/12] Run all statements in one transaction --- api/datastore/sql/sql.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/api/datastore/sql/sql.go b/api/datastore/sql/sql.go index 2a7981ef4..e427bb842 100644 --- a/api/datastore/sql/sql.go +++ b/api/datastore/sql/sql.go @@ -221,18 +221,17 @@ func (ds *sqlStore) UpdateApp(ctx context.Context, newapp *models.App) (*models. } func (ds *sqlStore) RemoveApp(ctx context.Context, appName string) error { - query := ds.db.Rebind(`DELETE FROM apps WHERE name = ?`) - res, err := ds.db.ExecContext(ctx, query, appName) - if _, err := res.RowsAffected(); err != nil { - return models.ErrAppsNotFound - } - _, err = ds.db.ExecContext(ctx, ds.db.Rebind( - `DELETE FROM logs WHERE app_name=?; + res, err := ds.db.ExecContext(ctx, ds.db.Rebind( + `DELETE FROM apps WHERE name = ?; + DELETE FROM logs WHERE app_name=?; DELETE FROM calls WHERE app_name=?; - DELETE FROM routes WHERE app_name=?;`), appName, appName, appName) + DELETE FROM routes WHERE app_name=?;`), appName, appName, appName, appName) if err != nil { return err } + if _, err := res.RowsAffected(); err != nil { + return models.ErrAppsNotFound + } return nil }