Skip to content

feat(coderd): add matched provisioner daemons information to more places #15688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Dec 2, 2024

Conversation

johnstcn
Copy link
Member

@johnstcn johnstcn commented Nov 29, 2024

  • Refactors checkProvisioners into db2sdk.MatchedProvisioners
  • Adds a separate RBAC subject just for reading provisioner daemons for this use-case
  • Adds matched provisioners information to more templateversion endpoints
  • Updates existing unit tests for template version endpoints
  • Updates wsbuilder to also return matched provisioners
  • Updates existing unit tests for workspaces API endpoints
  • Updates CLI to show a warning when creating a workspace for which no provisoners are available
  • Adds API endpoint for matched provisioners of template dry-run job
  • Local smoke-testing (stop and start external provisioner daemon while testing tagged templates/workspaces)

Comment on lines -1826 to -1855
func checkProvisioners(ctx context.Context, store database.Store, orgID uuid.UUID, wantTags map[string]string) (codersdk.MatchedProvisioners, error) {
// Check for eligible provisioners. This allows us to return a warning to the user if they
// submit a job for which no provisioner is available.
eligibleProvisioners, err := store.GetProvisionerDaemonsByOrganization(ctx, database.GetProvisionerDaemonsByOrganizationParams{
OrganizationID: orgID,
WantTags: wantTags,
})
if err != nil {
// Log the error but do not return any warnings. This is purely advisory and we should not block.
return codersdk.MatchedProvisioners{}, xerrors.Errorf("provisioner daemons by organization: %w", err)
}

staleInterval := time.Now().Add(-provisionerdserver.StaleInterval)
mostRecentlySeen := codersdk.NullTime{}
var matched codersdk.MatchedProvisioners
for _, provisioner := range eligibleProvisioners {
if !provisioner.LastSeenAt.Valid {
continue
}
matched.Count++
if provisioner.LastSeenAt.Time.After(staleInterval) {
matched.Available++
}
if provisioner.LastSeenAt.Time.After(mostRecentlySeen.Time) {
matched.MostRecentlySeen.Valid = true
matched.MostRecentlySeen.Time = provisioner.LastSeenAt.Time
}
}
return matched, nil
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self-review: extracted to db2sdk.

Comment on lines +379 to +384
// AsSystemReadProvisionerDaemons returns a context with an actor that has permissions
// to read provisioner daemons.
func AsSystemReadProvisionerDaemons(ctx context.Context) context.Context {
return context.WithValue(ctx, authContextKey{}, subjectSystemReadProvisionerDaemons)
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self-review: I wanted to avoid sprinkling dbauthz.AsSystemRestricted everywhere, so I made a separate RBAC role for when we just wish to read provisioner daemons. I can remove this and switch back to SystemRestricted if folks prefer.

@@ -245,7 +245,7 @@ func (e *Executor) runOnce(t time.Time) Stats {
}
}

nextBuild, job, err = builder.Build(e.ctx, tx, nil, audit.WorkspaceBuildBaggage{IP: "127.0.0.1"})
nextBuild, job, _, err = builder.Build(e.ctx, tx, nil, audit.WorkspaceBuildBaggage{IP: "127.0.0.1"})
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self-review: we may need to notify or log about this, but deferring for later.

Comment on lines +986 to +1002
var matchedProvisioners *codersdk.MatchedProvisioners
if jobs[0].ProvisionerJob.JobStatus == database.ProvisionerJobStatusPending {
// nolint: gocritic // The user hitting this endpoint may not have
// permission to read provisioner daemons, but we want to show them
// information about the provisioner daemons that are available.
provisioners, err := api.Database.GetProvisionerDaemonsByOrganization(dbauthz.AsSystemReadProvisionerDaemons(ctx), database.GetProvisionerDaemonsByOrganizationParams{
OrganizationID: jobs[0].ProvisionerJob.OrganizationID,
WantTags: jobs[0].ProvisionerJob.Tags,
})
if err != nil {
api.Logger.Error(ctx, "failed to fetch provisioners for job id", slog.F("job_id", jobs[0].ProvisionerJob.ID), slog.Error(err))
} else {
matchedProvisioners = ptr.Ref(db2sdk.MatchedProvisioners(provisioners, dbtime.Now(), provisionerdserver.StaleInterval))
}
}

httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(jobs[0]), matchedProvisioners, nil))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self-review: This addresses an issue I noticed in the frontend where it quickly "flashes" with the tag warning message but then gets overwritten when the FE re-requests the template version. Adding it to other template-related endpoints for posterity.

Comment on lines +53 to +58
if assert.Equal(t, tv.Job.Status, codersdk.ProvisionerJobPending) {
assert.NotNil(t, tv.MatchedProvisioners)
assert.Zero(t, tv.MatchedProvisioners.Available)
assert.Zero(t, tv.MatchedProvisioners.Count)
assert.False(t, tv.MatchedProvisioners.MostRecentlySeen.Valid)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zero because includeProvisionerDaemon is not set

Comment on lines +173 to +178
if assert.Equal(t, version.Job.Status, codersdk.ProvisionerJobPending) {
assert.NotNil(t, version.MatchedProvisioners)
assert.Equal(t, version.MatchedProvisioners.Available, 1)
assert.Equal(t, version.MatchedProvisioners.Count, 1)
assert.True(t, version.MatchedProvisioners.MostRecentlySeen.Valid)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

includeProvisionerDaemon is set here!

@@ -94,7 +95,8 @@ func TestBuilder_NoOptions(t *testing.T) {

ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
// nolint: dogsled
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self-review: we should probably disable this linter for unit tests

@johnstcn johnstcn changed the title Cj/more matching provisioners feat(coderd): add matched provisioner daemons information to more places Nov 29, 2024
@johnstcn johnstcn requested a review from SasSwart November 29, 2024 22:14
_, err = c.Client.CreateWorkspace(ctx, owner.OrganizationID, codersdk.Me, codersdk.CreateWorkspaceRequest{
_, err = c.Client.CreateUserWorkspace(ctx, codersdk.Me, codersdk.CreateWorkspaceRequest{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self-review: noticed this was usage of a deprecated function, drive-by fix.

Comment on lines -387 to +395
err = provisionerjobs.PostJob(api.Pubsub, *provisionerJob)
if err != nil {
// Client probably doesn't care about this error, so just log it.
api.Logger.Error(ctx, "failed to post provisioner job to pubsub", slog.Error(err))

if provisionerJob != nil {
if err := provisionerjobs.PostJob(api.Pubsub, *provisionerJob); err != nil {
// Client probably doesn't care about this error, so just log it.
api.Logger.Error(ctx, "failed to post provisioner job to pubsub", slog.Error(err))
}
Copy link
Member Author

@johnstcn johnstcn Dec 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self-review: linter complained about a possible nil ptr reference in pre-existing code.

require.NoError(t, closeDaemon.Close())
ctx := testutil.Context(t, testutil.WaitLong)
// Given: no provisioner daemons exist.
_, err := db.ExecContext(ctx, `DELETE FROM provisioner_daemons;`)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self-review: this is kinda yucky but I don't agree with the idea of creating database queries for things that will only ever get used intests.

@johnstcn johnstcn marked this pull request as ready for review December 2, 2024 08:59
@johnstcn johnstcn requested a review from dannykopping December 2, 2024 09:00
Copy link
Contributor

@dannykopping dannykopping left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@@ -60,6 +62,22 @@ func (api *API) templateVersion(rw http.ResponseWriter, r *http.Request) {
return
}

var matchedProvisioners *codersdk.MatchedProvisioners
if jobs[0].ProvisionerJob.JobStatus == database.ProvisionerJobStatusPending {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we only ever expect a single job to be returned by GetProvisionerJobsByIDsWithQueuePosition?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this instance, we should get either 0 or 1:

jobs, err := api.Database.GetProvisionerJobsByIDsWithQueuePosition(ctx, []uuid.UUID{templateVersion.JobID})

Copy link
Contributor

@dannykopping dannykopping Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point here is really that if we're only ever expecting a single job, we should consider changing the semantics of the GetProvisionerJobsByIDsWithQueuePosition to be a :one not a :many.
non-blocking suggestion, of course.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's possible though that a single template version could have multiple provisioner jobs associated if something went wrong.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we use the :many version of this query in a few places, and not just the :one.

Co-authored-by: Danny Kopping <danny@coder.com>
@johnstcn johnstcn merged commit 2b57dcc into main Dec 2, 2024
31 checks passed
@johnstcn johnstcn deleted the cj/more-matching-provisioners branch December 2, 2024 20:54
johnstcn added a commit that referenced this pull request Dec 2, 2024
johnstcn added a commit that referenced this pull request Dec 12, 2024
…ces (#15688)

- Refactors `checkProvisioners` into `db2sdk.MatchedProvisioners`
- Adds a separate RBAC subject just for reading provisioner daemons
- Adds matched provisioners information to additional endpoints relating to
  workspace builds and templates
-Updates existing unit tests for above endpoints
-Adds API endpoint for matched provisioners of template dry-run job
-Updates CLI to show warning when creating/starting/stopping/deleting
 workspaces for which no provisoners are available

---------

Co-authored-by: Danny Kopping <danny@coder.com>
(cherry picked from commit 2b57dcc)
johnstcn added a commit that referenced this pull request Dec 12, 2024
…ces (#15688)

- Refactors `checkProvisioners` into `db2sdk.MatchedProvisioners`
- Adds a separate RBAC subject just for reading provisioner daemons
- Adds matched provisioners information to additional endpoints relating to
  workspace builds and templates
-Updates existing unit tests for above endpoints
-Adds API endpoint for matched provisioners of template dry-run job
-Updates CLI to show warning when creating/starting/stopping/deleting
 workspaces for which no provisoners are available

---------

Co-authored-by: Danny Kopping <danny@coder.com>
(cherry picked from commit 2b57dcc)
johnstcn added a commit that referenced this pull request Dec 12, 2024
…ces (#15688)

- Refactors `checkProvisioners` into `db2sdk.MatchedProvisioners`
- Adds a separate RBAC subject just for reading provisioner daemons
- Adds matched provisioners information to additional endpoints relating to
  workspace builds and templates
-Updates existing unit tests for above endpoints
-Adds API endpoint for matched provisioners of template dry-run job
-Updates CLI to show warning when creating/starting/stopping/deleting
 workspaces for which no provisoners are available

---------

Co-authored-by: Danny Kopping <danny@coder.com>
(cherry picked from commit 2b57dcc)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants