mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: improve build deadline code (#19203)
- Adds/improves a lot of comments to make the autostop calculation code clearer - Changes the behavior of the enterprise template schedule store to match the behavior of the workspace TTL endpoint when the new TTL is zero - Fixes a bug in the workspace TTL endpoint where it could unset the build deadline, even though a max_deadline was specified - Adds a new constraint to the workspace_builds table that enforces the deadline is non-zero and below the max_deadline if it is set - Adds CHECK constraint enum generation to scripts/dbgen, used for testing the above constraint - Adds Dean and Danielle as CODEOWNERS for the autostop calculation code
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
// Code generated by scripts/dbgen/main.go. DO NOT EDIT.
|
||||
package database
|
||||
|
||||
// CheckConstraint represents a named check constraint on a table.
|
||||
type CheckConstraint string
|
||||
|
||||
// CheckConstraint enums.
|
||||
const (
|
||||
CheckOneTimePasscodeSet CheckConstraint = "one_time_passcode_set" // users
|
||||
CheckMaxProvisionerLogsLength CheckConstraint = "max_provisioner_logs_length" // provisioner_jobs
|
||||
CheckValidationMonotonicOrder CheckConstraint = "validation_monotonic_order" // template_version_parameters
|
||||
CheckMaxLogsLength CheckConstraint = "max_logs_length" // workspace_agents
|
||||
CheckSubsystemsNotNone CheckConstraint = "subsystems_not_none" // workspace_agents
|
||||
CheckWorkspaceBuildsAiTaskSidebarAppIDRequired CheckConstraint = "workspace_builds_ai_task_sidebar_app_id_required" // workspace_builds
|
||||
CheckWorkspaceBuildsDeadlineBelowMaxDeadline CheckConstraint = "workspace_builds_deadline_below_max_deadline" // workspace_builds
|
||||
)
|
||||
Generated
+2
-1
@@ -2224,7 +2224,8 @@ CREATE TABLE workspace_builds (
|
||||
template_version_preset_id uuid,
|
||||
has_ai_task boolean,
|
||||
ai_task_sidebar_app_id uuid,
|
||||
CONSTRAINT workspace_builds_ai_task_sidebar_app_id_required CHECK (((((has_ai_task IS NULL) OR (has_ai_task = false)) AND (ai_task_sidebar_app_id IS NULL)) OR ((has_ai_task = true) AND (ai_task_sidebar_app_id IS NOT NULL))))
|
||||
CONSTRAINT workspace_builds_ai_task_sidebar_app_id_required CHECK (((((has_ai_task IS NULL) OR (has_ai_task = false)) AND (ai_task_sidebar_app_id IS NULL)) OR ((has_ai_task = true) AND (ai_task_sidebar_app_id IS NOT NULL)))),
|
||||
CONSTRAINT workspace_builds_deadline_below_max_deadline CHECK ((((deadline <> '0001-01-01 00:00:00+00'::timestamp with time zone) AND (deadline <= max_deadline)) OR (max_deadline = '0001-01-01 00:00:00+00'::timestamp with time zone)))
|
||||
);
|
||||
|
||||
CREATE VIEW workspace_build_with_user AS
|
||||
|
||||
@@ -59,6 +59,28 @@ func IsForeignKeyViolation(err error, foreignKeyConstraints ...ForeignKeyConstra
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCheckViolation checks if the error is due to a check violation. If one or
|
||||
// more specific check constraints are given as arguments, the error must be
|
||||
// caused by one of them. If no constraints are given, this function returns
|
||||
// true for any check violation.
|
||||
func IsCheckViolation(err error, checkConstraints ...CheckConstraint) bool {
|
||||
var pqErr *pq.Error
|
||||
if errors.As(err, &pqErr) {
|
||||
if pqErr.Code.Name() == "check_violation" {
|
||||
if len(checkConstraints) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, cc := range checkConstraints {
|
||||
if pqErr.Constraint == string(cc) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsQueryCanceledError checks if the error is due to a query being canceled.
|
||||
func IsQueryCanceledError(err error) bool {
|
||||
var pqErr *pq.Error
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE workspace_builds
|
||||
DROP CONSTRAINT workspace_builds_deadline_below_max_deadline;
|
||||
@@ -0,0 +1,20 @@
|
||||
-- New constraint: (deadline IS NOT zero AND deadline <= max_deadline) UNLESS max_deadline is zero.
|
||||
-- Unfortunately, "zero" here means `time.Time{}`...
|
||||
|
||||
-- Update previous builds that would fail this new constraint. This matches the
|
||||
-- intended behaviour of the autostop algorithm.
|
||||
UPDATE
|
||||
workspace_builds
|
||||
SET
|
||||
deadline = max_deadline
|
||||
WHERE
|
||||
deadline > max_deadline
|
||||
AND max_deadline != '0001-01-01 00:00:00+00';
|
||||
|
||||
-- Add the new constraint.
|
||||
ALTER TABLE workspace_builds
|
||||
ADD CONSTRAINT workspace_builds_deadline_below_max_deadline
|
||||
CHECK (
|
||||
(deadline != '0001-01-01 00:00:00+00'::timestamptz AND deadline <= max_deadline)
|
||||
OR max_deadline = '0001-01-01 00:00:00+00'::timestamptz
|
||||
);
|
||||
@@ -6003,3 +6003,102 @@ func TestGetRunningPrebuiltWorkspaces(t *testing.T) {
|
||||
require.Len(t, runningPrebuilds, 1, "expected only one running prebuilt workspace")
|
||||
require.Equal(t, runningPrebuild.ID, runningPrebuilds[0].ID, "expected the running prebuilt workspace to be returned")
|
||||
}
|
||||
|
||||
func TestWorkspaceBuildDeadlineConstraint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
template := dbgen.Template(t, db, database.Template{
|
||||
CreatedBy: user.ID,
|
||||
OrganizationID: org.ID,
|
||||
})
|
||||
templateVersion := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{UUID: template.ID, Valid: true},
|
||||
OrganizationID: org.ID,
|
||||
CreatedBy: user.ID,
|
||||
})
|
||||
workspace := dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
OwnerID: user.ID,
|
||||
TemplateID: template.ID,
|
||||
Name: "test-workspace",
|
||||
Deleted: false,
|
||||
})
|
||||
job := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
|
||||
OrganizationID: org.ID,
|
||||
InitiatorID: database.PrebuildsSystemUserID,
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
StartedAt: sql.NullTime{Time: time.Now().Add(-time.Minute), Valid: true},
|
||||
CompletedAt: sql.NullTime{Time: time.Now(), Valid: true},
|
||||
})
|
||||
workspaceBuild := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
WorkspaceID: workspace.ID,
|
||||
TemplateVersionID: templateVersion.ID,
|
||||
JobID: job.ID,
|
||||
BuildNumber: 1,
|
||||
})
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
deadline time.Time
|
||||
maxDeadline time.Time
|
||||
expectOK bool
|
||||
}{
|
||||
{
|
||||
name: "no deadline or max_deadline",
|
||||
deadline: time.Time{},
|
||||
maxDeadline: time.Time{},
|
||||
expectOK: true,
|
||||
},
|
||||
{
|
||||
name: "deadline set when max_deadline is not set",
|
||||
deadline: time.Now().Add(time.Hour),
|
||||
maxDeadline: time.Time{},
|
||||
expectOK: true,
|
||||
},
|
||||
{
|
||||
name: "deadline before max_deadline",
|
||||
deadline: time.Now().Add(-time.Hour),
|
||||
maxDeadline: time.Now().Add(time.Hour),
|
||||
expectOK: true,
|
||||
},
|
||||
{
|
||||
name: "deadline is max_deadline",
|
||||
deadline: time.Now().Add(time.Hour),
|
||||
maxDeadline: time.Now().Add(time.Hour),
|
||||
expectOK: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "deadline after max_deadline",
|
||||
deadline: time.Now().Add(time.Hour),
|
||||
maxDeadline: time.Now().Add(-time.Hour),
|
||||
expectOK: false,
|
||||
},
|
||||
{
|
||||
name: "deadline is not set when max_deadline is set",
|
||||
deadline: time.Time{},
|
||||
maxDeadline: time.Now().Add(time.Hour),
|
||||
expectOK: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
err := db.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
|
||||
ID: workspaceBuild.ID,
|
||||
Deadline: c.deadline,
|
||||
MaxDeadline: c.maxDeadline,
|
||||
UpdatedAt: time.Now(),
|
||||
})
|
||||
if c.expectOK {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.True(t, database.IsCheckViolation(err, database.CheckWorkspaceBuildsDeadlineBelowMaxDeadline))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user