Skip to content

Commit 687d953

Browse files
authored
chore: provisioner acquirer to respect organization ID of jobs (coder#13874)
* test: add unit test to verify creation of templates in multiple orgs * chore: provisioner acquirer to respect organization ID of jobs Prior to this the wrong provisioner was awakened on any new job posting. * add comment and stricter check
1 parent bee913a commit 687d953

File tree

3 files changed

+72
-14
lines changed

3 files changed

+72
-14
lines changed

coderd/database/provisionerjobs/provisionerjobs.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package provisionerjobs
33
import (
44
"encoding/json"
55

6+
"github.com/google/uuid"
67
"golang.org/x/xerrors"
78

89
"github.com/coder/coder/v2/coderd/database"
@@ -12,12 +13,14 @@ import (
1213
const EventJobPosted = "provisioner_job_posted"
1314

1415
type JobPosting struct {
16+
OrganizationID uuid.UUID `json:"organization_id"`
1517
ProvisionerType database.ProvisionerType `json:"type"`
1618
Tags map[string]string `json:"tags"`
1719
}
1820

1921
func PostJob(ps pubsub.Pubsub, job database.ProvisionerJob) error {
2022
msg, err := json.Marshal(JobPosting{
23+
OrganizationID: job.OrganizationID,
2124
ProvisionerType: job.Provisioner,
2225
Tags: job.Tags,
2326
})

coderd/provisionerdserver/acquirer.go

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,14 @@ func (a *Acquirer) want(organization uuid.UUID, pt []database.ProvisionerType, t
163163
if !ok {
164164
ctx, cancel := context.WithCancel(a.ctx)
165165
d = domain{
166-
ctx: ctx,
167-
cancel: cancel,
168-
a: a,
169-
key: dk,
170-
pt: pt,
171-
tags: tags,
172-
acquirees: make(map[chan<- struct{}]*acquiree),
166+
ctx: ctx,
167+
cancel: cancel,
168+
a: a,
169+
key: dk,
170+
pt: pt,
171+
tags: tags,
172+
organizationID: organization,
173+
acquirees: make(map[chan<- struct{}]*acquiree),
173174
}
174175
a.q[dk] = d
175176
go d.poll(a.backupPollDuration)
@@ -450,16 +451,22 @@ type acquiree struct {
450451
// tags. Acquirees in the same domain are restricted such that only one queries
451452
// the database at a time.
452453
type domain struct {
453-
ctx context.Context
454-
cancel context.CancelFunc
455-
a *Acquirer
456-
key dKey
457-
pt []database.ProvisionerType
458-
tags Tags
459-
acquirees map[chan<- struct{}]*acquiree
454+
ctx context.Context
455+
cancel context.CancelFunc
456+
a *Acquirer
457+
key dKey
458+
pt []database.ProvisionerType
459+
tags Tags
460+
organizationID uuid.UUID
461+
acquirees map[chan<- struct{}]*acquiree
460462
}
461463

462464
func (d domain) contains(p provisionerjobs.JobPosting) bool {
465+
// If the organization ID is 'uuid.Nil', this is a legacy job posting.
466+
// Ignore this check in the legacy case.
467+
if p.OrganizationID != uuid.Nil && p.OrganizationID != d.organizationID {
468+
return false
469+
}
463470
if !slices.Contains(d.pt, p.ProvisionerType) {
464471
return false
465472
}

enterprise/coderd/templates_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,3 +1829,51 @@ func TestTemplateAccess(t *testing.T) {
18291829
}
18301830
})
18311831
}
1832+
1833+
func TestMultipleOrganizationTemplates(t *testing.T) {
1834+
t.Parallel()
1835+
1836+
ownerClient, first := coderdenttest.New(t, &coderdenttest.Options{
1837+
Options: &coderdtest.Options{
1838+
// This only affects the first org.
1839+
IncludeProvisionerDaemon: true,
1840+
},
1841+
LicenseOptions: &coderdenttest.LicenseOptions{
1842+
Features: license.Features{
1843+
codersdk.FeatureExternalProvisionerDaemons: 1,
1844+
},
1845+
},
1846+
})
1847+
1848+
templateAdmin, _ := coderdtest.CreateAnotherUser(t, ownerClient, first.OrganizationID, rbac.RoleTemplateAdmin())
1849+
1850+
second := coderdtest.CreateOrganization(t, ownerClient, coderdtest.CreateOrganizationOptions{
1851+
IncludeProvisionerDaemon: true,
1852+
})
1853+
1854+
third := coderdtest.CreateOrganization(t, ownerClient, coderdtest.CreateOrganizationOptions{
1855+
IncludeProvisionerDaemon: true,
1856+
})
1857+
1858+
t.Logf("First organization: %s", first.OrganizationID.String())
1859+
t.Logf("Second organization: %s", second.ID.String())
1860+
t.Logf("Third organization: %s", third.ID.String())
1861+
1862+
t.Logf("Creating template version in second organization")
1863+
1864+
start := time.Now()
1865+
version := coderdtest.CreateTemplateVersion(t, templateAdmin, second.ID, nil)
1866+
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version.ID)
1867+
coderdtest.CreateTemplate(t, templateAdmin, second.ID, version.ID, func(request *codersdk.CreateTemplateRequest) {
1868+
request.Name = "random"
1869+
})
1870+
1871+
if time.Since(start) > time.Second*10 {
1872+
// The test can sometimes pass because 'AwaitTemplateVersionJobCompleted'
1873+
// allows 25s, and the provisioner will check every 30s if not awakened
1874+
// from the pubsub. So there is a chance it will pass. If it takes longer
1875+
// than 10s, then it's a problem. The provisioner is not getting clearance.
1876+
t.Error("Creating template version in second organization took too long")
1877+
t.FailNow()
1878+
}
1879+
}

0 commit comments

Comments
 (0)