Skip to content

Commit 465e0d8

Browse files
committed
Terraform tests pass!
1 parent 658f5b2 commit 465e0d8

26 files changed

+528
-365
lines changed

agent/agent.go

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,12 @@ func (a *agent) runLoop(ctx context.Context) {
207207
}
208208
}
209209

210-
func (*agent) collectMetadata(ctx context.Context, md agentsdk.Metadata) agentsdk.MetadataResult {
211-
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(md.Interval))
210+
func collectMetadata(ctx context.Context, md agentsdk.MetadataDescription) agentsdk.MetadataResult {
211+
timeout := md.Timeout
212+
if timeout == 0 {
213+
timeout = md.Interval
214+
}
215+
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
212216
defer cancel()
213217

214218
collectedAt := time.Now()
@@ -219,25 +223,28 @@ func (*agent) collectMetadata(ctx context.Context, md agentsdk.Metadata) agentsd
219223
cmd := exec.CommandContext(ctx, md.Cmd[0], md.Cmd[1:]...)
220224
cmd.Stdout = &out
221225
cmd.Stderr = &out
226+
227+
// The error isn't mutually exclusive with useful output.
222228
err := cmd.Run()
223-
if err != nil {
224-
return agentsdk.MetadataResult{
225-
CollectedAt: collectedAt,
226-
Key: md.Key,
227-
Error: err.Error(),
228-
}
229-
}
230229

231230
const bufLimit = 10 << 14
232231
if out.Len() > bufLimit {
232+
err = errors.Join(
233+
err,
234+
xerrors.Errorf("output truncated from %v to %v bytes", out.Len(), bufLimit),
235+
)
233236
out.Truncate(bufLimit)
234237
}
235238

236-
return agentsdk.MetadataResult{
239+
result := agentsdk.MetadataResult{
237240
CollectedAt: collectedAt,
238241
Key: md.Key,
239242
Value: out.String(),
240243
}
244+
if err != nil {
245+
result.Error = err.Error()
246+
}
247+
return result
241248
}
242249

243250
func (a *agent) reportMetadataLoop(ctx context.Context) {
@@ -271,9 +278,10 @@ func (a *agent) reportMetadataLoop(ctx context.Context) {
271278
continue
272279
}
273280
// If the manifest changes (e.g. on agent reconnect) we need to
274-
// purge old values.
281+
// purge old cache values to prevent lastCollectedAt from growing
282+
// boundlessly.
275283
for key := range lastCollectedAts {
276-
if slices.IndexFunc(manifest.Metadata, func(md agentsdk.Metadata) bool {
284+
if slices.IndexFunc(manifest.Metadata, func(md agentsdk.MetadataDescription) bool {
277285
return md.Key == key
278286
}) < 0 {
279287
delete(lastCollectedAts, key)
@@ -296,14 +304,17 @@ func (a *agent) reportMetadataLoop(ctx context.Context) {
296304
}
297305
if len(metadataResults) > cap(metadataResults)/2 {
298306
// If we're backpressured on sending back results, we risk
299-
// runaway goroutine growth or overloading coderd.
307+
// runaway goroutine growth and/or overloading coderd. So,
308+
// we just skip the collection. Since we never update
309+
// "lastCollectedAt" for this key, we'll retry the collection
310+
// on the next tick.
300311
continue
301312
}
302-
go func(md agentsdk.Metadata) {
313+
go func(md agentsdk.MetadataDescription) {
303314
select {
304315
case <-ctx.Done():
305316
return
306-
case metadataResults <- a.collectMetadata(ctx, md):
317+
case metadataResults <- collectMetadata(ctx, md):
307318
}
308319
}(md)
309320
}

agent/agent_test.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -778,26 +778,24 @@ func TestAgent_Metadata(t *testing.T) {
778778

779779
t.Run("Basic", func(t *testing.T) {
780780
t.Parallel()
781-
//nolint:dogsled
782781
dir := t.TempDir()
782+
const reportInterval = time.Millisecond * 200
783783
greetingPath := filepath.Join(dir, "greeting")
784784
_, client, _, _, _ := setupAgent(t, agentsdk.Manifest{
785-
Metadata: []agentsdk.Metadata{
785+
Metadata: []agentsdk.MetadataDescription{
786786
{
787787
Key: "greeting",
788-
Interval: time.Millisecond * 100,
789-
Cmd: []string{"sh", "-c", "echo hello | tee " + greetingPath},
788+
Interval: reportInterval,
789+
Cmd: []string{"sh", "-c", "echo hello | tee -a " + greetingPath},
790790
},
791791
{
792792
Key: "bad",
793-
Interval: time.Millisecond * 100,
793+
Interval: reportInterval,
794794
Cmd: []string{"sh", "-c", "exit 1"},
795795
},
796796
},
797797
}, 0)
798798

799-
start := time.Now()
800-
801799
require.Eventually(t, func() bool {
802800
return len(client.getMetadata()) == 2
803801
}, testutil.WaitShort, testutil.IntervalMedium)
@@ -808,25 +806,40 @@ func TestAgent_Metadata(t *testing.T) {
808806
panic("unexpected number of metadata entries")
809807
}
810808

811-
require.Equal(t, "hello", md["greeting"].Value)
809+
require.Equal(t, "hello\n", md["greeting"].Value)
812810
require.Equal(t, "exit status 1", md["bad"].Error)
813811

814812
greetingByt, err := os.ReadFile(greetingPath)
815813
require.NoError(t, err)
816814

817815
var (
818816
numGreetings = bytes.Count(greetingByt, []byte("hello"))
819-
idealNumGreetings = time.Since(start) / (time.Millisecond * 100)
817+
idealNumGreetings = time.Since(start) / (reportInterval)
818+
upperBound = int(idealNumGreetings) + 1
819+
lowerBound = (int(idealNumGreetings) / 2)
820820
)
821821

822+
if idealNumGreetings < 5 {
823+
// Not enough time has passed to get a good sample size.
824+
continue
825+
}
826+
827+
t.Logf("numGreetings: %d, idealNumGreetings: %d", numGreetings, idealNumGreetings)
828+
// The report loop may slow down on load, but it should never, ever
829+
// speed up.
830+
if numGreetings > upperBound {
831+
t.Fatalf("too many greetings: %d > %d", numGreetings, upperBound)
832+
} else if numGreetings < lowerBound {
833+
t.Fatalf("too few greetings: %d < %d", numGreetings, lowerBound)
834+
}
822835
}
823836
})
824837

825838
t.Run("CollectOnce", func(t *testing.T) {
826839
t.Parallel()
827840
//nolint:dogsled
828841
_, client, _, _, _ := setupAgent(t, agentsdk.Manifest{
829-
Metadata: []agentsdk.Metadata{
842+
Metadata: []agentsdk.MetadataDescription{
830843
{
831844
Key: "greeting",
832845
Interval: 0,

coderd/apidoc/docs.go

Lines changed: 31 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 31 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/wsconncache/wsconncache_test.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,10 @@ func TestCache(t *testing.T) {
154154
})
155155
}
156156

157-
func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Duration) *codersdk.WorkspaceAgentConn {
157+
func setupAgent(t *testing.T, manifest agentsdk.Manifest, ptyTimeout time.Duration) *codersdk.WorkspaceAgentConn {
158158
t.Helper()
159159

160-
metadata.DERPMap = tailnettest.RunDERPAndSTUN(t)
160+
manifest.DERPMap = tailnettest.RunDERPAndSTUN(t)
161161

162162
coordinator := tailnet.NewCoordinator()
163163
t.Cleanup(func() {
@@ -168,7 +168,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Durati
168168
Client: &client{
169169
t: t,
170170
agentID: agentID,
171-
manifest: metadata,
171+
manifest: manifest,
172172
coordinator: coordinator,
173173
},
174174
Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelInfo),
@@ -179,7 +179,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Durati
179179
})
180180
conn, err := tailnet.NewConn(&tailnet.Options{
181181
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
182-
DERPMap: metadata.DERPMap,
182+
DERPMap: manifest.DERPMap,
183183
Logger: slogtest.Make(t, nil).Named("tailnet").Leveled(slog.LevelDebug),
184184
})
185185
require.NoError(t, err)
@@ -246,6 +246,10 @@ func (*client) PostAppHealth(_ context.Context, _ agentsdk.PostAppHealthsRequest
246246
return nil
247247
}
248248

249+
func (*client) PostMetadata(_ context.Context, _ agentsdk.PostMetadataRequest) error {
250+
return nil
251+
}
252+
249253
func (*client) PostStartup(_ context.Context, _ agentsdk.PostStartupRequest) error {
250254
return nil
251255
}

codersdk/agentsdk/agentsdk.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@ func (c *Client) GitSSHKey(ctx context.Context) (GitSSHKey, error) {
6565
return gitSSHKey, json.NewDecoder(res.Body).Decode(&gitSSHKey)
6666
}
6767

68-
// Metadata is a description of dynamic metadata the agent should report
68+
// MetadataDescription is a description of dynamic metadata the agent should report
6969
// back to coderd. It is provided via the `metadata` list in the `coder_agent`
7070
// block.
71-
type Metadata struct {
71+
type MetadataDescription struct {
7272
Key string
7373
Cmd []string
7474
Interval time.Duration
75+
Timeout time.Duration
7576
}
7677

7778
type MetadataResult struct {
@@ -114,7 +115,7 @@ type Manifest struct {
114115
MOTDFile string `json:"motd_file"`
115116
ShutdownScript string `json:"shutdown_script"`
116117
ShutdownScriptTimeout time.Duration `json:"shutdown_script_timeout"`
117-
Metadata []Metadata `json:"dynamic_metadata"`
118+
Metadata []MetadataDescription `json:"dynamic_metadata"`
118119
}
119120

120121
// Manifest fetches manifest for the currently authenticated workspace agent.

0 commit comments

Comments
 (0)