feat: add activity bumping to template scheduling (#9040)

This commit is contained in:
Jon Ayers
2023-08-22 15:15:13 -05:00
committed by GitHub
parent 6214117d3d
commit 6e41cd1eda
30 changed files with 672 additions and 193 deletions

View File

@ -2385,6 +2385,14 @@ func (q *querier) UpdateTemplateVersionGitAuthProvidersByJobID(ctx context.Conte
return q.db.UpdateTemplateVersionGitAuthProvidersByJobID(ctx, arg)
}
func (q *querier) UpdateTemplateWorkspacesLastUsedAt(ctx context.Context, arg database.UpdateTemplateWorkspacesLastUsedAtParams) error {
fetch := func(ctx context.Context, arg database.UpdateTemplateWorkspacesLastUsedAtParams) (database.Template, error) {
return q.db.GetTemplateByID(ctx, arg.TemplateID)
}
return fetchAndExec(q.log, q.auth, rbac.ActionUpdate, fetch, q.db.UpdateTemplateWorkspacesLastUsedAt)(ctx, arg)
}
// UpdateUserDeletedByID
// Deprecated: Delete this function in favor of 'SoftDeleteUserByID'. Deletes are
// irreversible.
@ -2663,12 +2671,12 @@ func (q *querier) UpdateWorkspaceTTL(ctx context.Context, arg database.UpdateWor
return update(q.log, q.auth, fetch, q.db.UpdateWorkspaceTTL)(ctx, arg)
}
func (q *querier) UpdateWorkspacesDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesDeletingAtByTemplateIDParams) error {
fetch := func(ctx context.Context, arg database.UpdateWorkspacesDeletingAtByTemplateIDParams) (database.Template, error) {
func (q *querier) UpdateWorkspacesLockedDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesLockedDeletingAtByTemplateIDParams) error {
fetch := func(ctx context.Context, arg database.UpdateWorkspacesLockedDeletingAtByTemplateIDParams) (database.Template, error) {
return q.db.GetTemplateByID(ctx, arg.TemplateID)
}
return fetchAndExec(q.log, q.auth, rbac.ActionUpdate, fetch, q.db.UpdateWorkspacesDeletingAtByTemplateID)(ctx, arg)
return fetchAndExec(q.log, q.auth, rbac.ActionUpdate, fetch, q.db.UpdateWorkspacesLockedDeletingAtByTemplateID)(ctx, arg)
}
func (q *querier) UpsertAppSecurityKey(ctx context.Context, data string) error {

View File

@ -5199,6 +5199,26 @@ func (q *FakeQuerier) UpdateTemplateVersionGitAuthProvidersByJobID(_ context.Con
return sql.ErrNoRows
}
func (q *FakeQuerier) UpdateTemplateWorkspacesLastUsedAt(_ context.Context, arg database.UpdateTemplateWorkspacesLastUsedAtParams) error {
err := validateDatabaseType(arg)
if err != nil {
return err
}
q.mutex.Lock()
defer q.mutex.Unlock()
for i, ws := range q.workspaces {
if ws.TemplateID != arg.TemplateID {
continue
}
ws.LastUsedAt = arg.LastUsedAt
q.workspaces[i] = ws
}
return nil
}
func (q *FakeQuerier) UpdateUserDeletedByID(_ context.Context, params database.UpdateUserDeletedByIDParams) error {
if err := validateDatabaseType(params); err != nil {
return err
@ -5796,7 +5816,7 @@ func (q *FakeQuerier) UpdateWorkspaceTTL(_ context.Context, arg database.UpdateW
return sql.ErrNoRows
}
func (q *FakeQuerier) UpdateWorkspacesDeletingAtByTemplateID(_ context.Context, arg database.UpdateWorkspacesDeletingAtByTemplateIDParams) error {
func (q *FakeQuerier) UpdateWorkspacesLockedDeletingAtByTemplateID(_ context.Context, arg database.UpdateWorkspacesLockedDeletingAtByTemplateIDParams) error {
q.mutex.Lock()
defer q.mutex.Unlock()
@ -5806,9 +5826,21 @@ func (q *FakeQuerier) UpdateWorkspacesDeletingAtByTemplateID(_ context.Context,
}
for i, ws := range q.workspaces {
if ws.TemplateID != arg.TemplateID {
continue
}
if ws.LockedAt.Time.IsZero() {
continue
}
if !arg.LockedAt.IsZero() {
ws.LockedAt = sql.NullTime{
Valid: true,
Time: arg.LockedAt,
}
}
deletingAt := sql.NullTime{
Valid: arg.LockedTtlMs > 0,
}

View File

@ -1453,6 +1453,13 @@ func (m metricsStore) UpdateTemplateVersionGitAuthProvidersByJobID(ctx context.C
return err
}
func (m metricsStore) UpdateTemplateWorkspacesLastUsedAt(ctx context.Context, arg database.UpdateTemplateWorkspacesLastUsedAtParams) error {
start := time.Now()
r0 := m.s.UpdateTemplateWorkspacesLastUsedAt(ctx, arg)
m.queryLatencies.WithLabelValues("UpdateTemplateWorkspacesLastUsedAt").Observe(time.Since(start).Seconds())
return r0
}
func (m metricsStore) UpdateUserDeletedByID(ctx context.Context, arg database.UpdateUserDeletedByIDParams) error {
start := time.Now()
err := m.s.UpdateUserDeletedByID(ctx, arg)
@ -1635,10 +1642,10 @@ func (m metricsStore) UpdateWorkspaceTTL(ctx context.Context, arg database.Updat
return r0
}
func (m metricsStore) UpdateWorkspacesDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesDeletingAtByTemplateIDParams) error {
func (m metricsStore) UpdateWorkspacesLockedDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesLockedDeletingAtByTemplateIDParams) error {
start := time.Now()
r0 := m.s.UpdateWorkspacesDeletingAtByTemplateID(ctx, arg)
m.queryLatencies.WithLabelValues("UpdateWorkspacesDeletingAtByTemplateID").Observe(time.Since(start).Seconds())
r0 := m.s.UpdateWorkspacesLockedDeletingAtByTemplateID(ctx, arg)
m.queryLatencies.WithLabelValues("UpdateWorkspacesLockedDeletingAtByTemplateID").Observe(time.Since(start).Seconds())
return r0
}

View File

@ -3062,6 +3062,20 @@ func (mr *MockStoreMockRecorder) UpdateTemplateVersionGitAuthProvidersByJobID(ar
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTemplateVersionGitAuthProvidersByJobID", reflect.TypeOf((*MockStore)(nil).UpdateTemplateVersionGitAuthProvidersByJobID), arg0, arg1)
}
// UpdateTemplateWorkspacesLastUsedAt mocks base method.
func (m *MockStore) UpdateTemplateWorkspacesLastUsedAt(arg0 context.Context, arg1 database.UpdateTemplateWorkspacesLastUsedAtParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateTemplateWorkspacesLastUsedAt", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateTemplateWorkspacesLastUsedAt indicates an expected call of UpdateTemplateWorkspacesLastUsedAt.
func (mr *MockStoreMockRecorder) UpdateTemplateWorkspacesLastUsedAt(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTemplateWorkspacesLastUsedAt", reflect.TypeOf((*MockStore)(nil).UpdateTemplateWorkspacesLastUsedAt), arg0, arg1)
}
// UpdateUserDeletedByID mocks base method.
func (m *MockStore) UpdateUserDeletedByID(arg0 context.Context, arg1 database.UpdateUserDeletedByIDParams) error {
m.ctrl.T.Helper()
@ -3437,18 +3451,18 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceTTL(arg0, arg1 interface{}) *gom
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceTTL", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceTTL), arg0, arg1)
}
// UpdateWorkspacesDeletingAtByTemplateID mocks base method.
func (m *MockStore) UpdateWorkspacesDeletingAtByTemplateID(arg0 context.Context, arg1 database.UpdateWorkspacesDeletingAtByTemplateIDParams) error {
// UpdateWorkspacesLockedDeletingAtByTemplateID mocks base method.
func (m *MockStore) UpdateWorkspacesLockedDeletingAtByTemplateID(arg0 context.Context, arg1 database.UpdateWorkspacesLockedDeletingAtByTemplateIDParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateWorkspacesDeletingAtByTemplateID", arg0, arg1)
ret := m.ctrl.Call(m, "UpdateWorkspacesLockedDeletingAtByTemplateID", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateWorkspacesDeletingAtByTemplateID indicates an expected call of UpdateWorkspacesDeletingAtByTemplateID.
func (mr *MockStoreMockRecorder) UpdateWorkspacesDeletingAtByTemplateID(arg0, arg1 interface{}) *gomock.Call {
// UpdateWorkspacesLockedDeletingAtByTemplateID indicates an expected call of UpdateWorkspacesLockedDeletingAtByTemplateID.
func (mr *MockStoreMockRecorder) UpdateWorkspacesLockedDeletingAtByTemplateID(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspacesDeletingAtByTemplateID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspacesDeletingAtByTemplateID), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspacesLockedDeletingAtByTemplateID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspacesLockedDeletingAtByTemplateID), arg0, arg1)
}
// UpsertAppSecurityKey mocks base method.

View File

@ -270,6 +270,7 @@ type sqlcQuerier interface {
UpdateTemplateVersionByID(ctx context.Context, arg UpdateTemplateVersionByIDParams) error
UpdateTemplateVersionDescriptionByJobID(ctx context.Context, arg UpdateTemplateVersionDescriptionByJobIDParams) error
UpdateTemplateVersionGitAuthProvidersByJobID(ctx context.Context, arg UpdateTemplateVersionGitAuthProvidersByJobIDParams) error
UpdateTemplateWorkspacesLastUsedAt(ctx context.Context, arg UpdateTemplateWorkspacesLastUsedAtParams) error
UpdateUserDeletedByID(ctx context.Context, arg UpdateUserDeletedByIDParams) error
UpdateUserHashedPassword(ctx context.Context, arg UpdateUserHashedPasswordParams) error
UpdateUserLastSeenAt(ctx context.Context, arg UpdateUserLastSeenAtParams) (User, error)
@ -297,7 +298,7 @@ type sqlcQuerier interface {
UpdateWorkspaceProxy(ctx context.Context, arg UpdateWorkspaceProxyParams) (WorkspaceProxy, error)
UpdateWorkspaceProxyDeleted(ctx context.Context, arg UpdateWorkspaceProxyDeletedParams) error
UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error
UpdateWorkspacesDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesDeletingAtByTemplateIDParams) error
UpdateWorkspacesLockedDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesLockedDeletingAtByTemplateIDParams) error
UpsertAppSecurityKey(ctx context.Context, value string) error
// The default proxy is implied and not actually stored in the database.
// So we need to store it's configuration here for display purposes.

View File

@ -9786,6 +9786,24 @@ func (q *sqlQuerier) InsertWorkspace(ctx context.Context, arg InsertWorkspacePar
return i, err
}
const updateTemplateWorkspacesLastUsedAt = `-- name: UpdateTemplateWorkspacesLastUsedAt :exec
UPDATE workspaces
SET
last_used_at = $1::timestamptz
WHERE
template_id = $2
`
type UpdateTemplateWorkspacesLastUsedAtParams struct {
LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"`
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
}
func (q *sqlQuerier) UpdateTemplateWorkspacesLastUsedAt(ctx context.Context, arg UpdateTemplateWorkspacesLastUsedAtParams) error {
_, err := q.db.ExecContext(ctx, updateTemplateWorkspacesLastUsedAt, arg.LastUsedAt, arg.TemplateID)
return err
}
const updateWorkspace = `-- name: UpdateWorkspace :one
UPDATE
workspaces
@ -9945,23 +9963,28 @@ func (q *sqlQuerier) UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspace
return err
}
const updateWorkspacesDeletingAtByTemplateID = `-- name: UpdateWorkspacesDeletingAtByTemplateID :exec
UPDATE
workspaces
const updateWorkspacesLockedDeletingAtByTemplateID = `-- name: UpdateWorkspacesLockedDeletingAtByTemplateID :exec
UPDATE workspaces
SET
deleting_at = CASE WHEN $1::bigint = 0 THEN NULL ELSE locked_at + interval '1 milliseconds' * $1::bigint END
deleting_at = CASE
WHEN $1::bigint = 0 THEN NULL
WHEN $2::timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN ($2::timestamptz) + interval '1 milliseconds' * $1::bigint
ELSE locked_at + interval '1 milliseconds' * $1::bigint
END,
locked_at = CASE WHEN $2::timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN $2::timestamptz ELSE locked_at END
WHERE
template_id = $2
template_id = $3
AND
locked_at IS NOT NULL
locked_at IS NOT NULL
`
type UpdateWorkspacesDeletingAtByTemplateIDParams struct {
type UpdateWorkspacesLockedDeletingAtByTemplateIDParams struct {
LockedTtlMs int64 `db:"locked_ttl_ms" json:"locked_ttl_ms"`
LockedAt time.Time `db:"locked_at" json:"locked_at"`
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
}
func (q *sqlQuerier) UpdateWorkspacesDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesDeletingAtByTemplateIDParams) error {
_, err := q.db.ExecContext(ctx, updateWorkspacesDeletingAtByTemplateID, arg.LockedTtlMs, arg.TemplateID)
func (q *sqlQuerier) UpdateWorkspacesLockedDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesLockedDeletingAtByTemplateIDParams) error {
_, err := q.db.ExecContext(ctx, updateWorkspacesLockedDeletingAtByTemplateID, arg.LockedTtlMs, arg.LockedAt, arg.TemplateID)
return err
}

View File

@ -512,12 +512,23 @@ AND
workspaces.id = $1
RETURNING workspaces.*;
-- name: UpdateWorkspacesDeletingAtByTemplateID :exec
UPDATE
workspaces
-- name: UpdateWorkspacesLockedDeletingAtByTemplateID :exec
UPDATE workspaces
SET
deleting_at = CASE WHEN @locked_ttl_ms::bigint = 0 THEN NULL ELSE locked_at + interval '1 milliseconds' * @locked_ttl_ms::bigint END
deleting_at = CASE
WHEN @locked_ttl_ms::bigint = 0 THEN NULL
WHEN @locked_at::timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN (@locked_at::timestamptz) + interval '1 milliseconds' * @locked_ttl_ms::bigint
ELSE locked_at + interval '1 milliseconds' * @locked_ttl_ms::bigint
END,
locked_at = CASE WHEN @locked_at::timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN @locked_at::timestamptz ELSE locked_at END
WHERE
template_id = @template_id
template_id = @template_id
AND
locked_at IS NOT NULL;
locked_at IS NOT NULL;
-- name: UpdateTemplateWorkspacesLastUsedAt :exec
UPDATE workspaces
SET
last_used_at = @last_used_at::timestamptz
WHERE
template_id = @template_id;

View File

@ -105,6 +105,18 @@ type TemplateScheduleOptions struct {
// LockedTTL dictates the duration after which locked workspaces will be
// permanently deleted.
LockedTTL time.Duration `json:"locked_ttl"`
// UpdateWorkspaceLastUsedAt updates the template's workspaces'
// last_used_at field. This is useful for preventing updates to the
// templates inactivity_ttl immediately triggering a lock action against
// workspaces whose last_used_at field violates the new template
// inactivity_ttl threshold.
UpdateWorkspaceLastUsedAt bool `json:"update_workspace_last_used_at"`
// UpdateWorkspaceLockedAt updates the template's workspaces'
// locked_at field. This is useful for preventing updates to the
// templates locked_ttl immediately triggering a delete action against
// workspaces whose locked_at field violates the new template locked_ttl
// threshold.
UpdateWorkspaceLockedAt bool `json:"update_workspace_locked_at"`
}
// TemplateScheduleStore provides an interface for retrieving template

View File

@ -622,9 +622,11 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
DaysOfWeek: restartRequirementDaysOfWeekParsed,
Weeks: req.RestartRequirement.Weeks,
},
FailureTTL: failureTTL,
InactivityTTL: inactivityTTL,
LockedTTL: lockedTTL,
FailureTTL: failureTTL,
InactivityTTL: inactivityTTL,
LockedTTL: lockedTTL,
UpdateWorkspaceLastUsedAt: req.UpdateWorkspaceLastUsedAt,
UpdateWorkspaceLockedAt: req.UpdateWorkspaceLockedAt,
})
if err != nil {
return xerrors.Errorf("set template schedule options: %w", err)