Skip to content

Commit 9281333

Browse files
committed
Merge remote-tracking branch 'origin/main' into stevenmasley/regions
2 parents f9446c2 + 87b7537 commit 9281333

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1665
-354
lines changed

agent/agent.go

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type Options struct {
6060
ReconnectingPTYTimeout time.Duration
6161
EnvironmentVariables map[string]string
6262
Logger slog.Logger
63-
AgentPorts map[int]string
63+
IgnorePorts map[int]string
6464
SSHMaxTimeout time.Duration
6565
TailnetListenPort uint16
6666
}
@@ -76,7 +76,12 @@ type Client interface {
7676
PatchStartupLogs(ctx context.Context, req agentsdk.PatchStartupLogs) error
7777
}
7878

79-
func New(options Options) io.Closer {
79+
type Agent interface {
80+
HTTPDebug() http.Handler
81+
io.Closer
82+
}
83+
84+
func New(options Options) Agent {
8085
if options.ReconnectingPTYTimeout == 0 {
8186
options.ReconnectingPTYTimeout = 5 * time.Minute
8287
}
@@ -112,7 +117,7 @@ func New(options Options) io.Closer {
112117
tempDir: options.TempDir,
113118
lifecycleUpdate: make(chan struct{}, 1),
114119
lifecycleReported: make(chan codersdk.WorkspaceAgentLifecycle, 1),
115-
ignorePorts: options.AgentPorts,
120+
ignorePorts: options.IgnorePorts,
116121
connStatsChan: make(chan *agentsdk.Stats, 1),
117122
sshMaxTimeout: options.SSHMaxTimeout,
118123
}
@@ -1036,12 +1041,9 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, logger slog.Logger, m
10361041
<-ctx.Done()
10371042
_ = process.Kill()
10381043
}()
1039-
go func() {
1040-
// If the process dies randomly, we should
1041-
// close the pty.
1042-
_ = process.Wait()
1043-
rpty.Close()
1044-
}()
1044+
// We don't need to separately monitor for the process exiting.
1045+
// When it exits, our ptty.OutputReader() will return EOF after
1046+
// reading all process output.
10451047
if err = a.trackConnGoroutine(func() {
10461048
buffer := make([]byte, 1024)
10471049
for {
@@ -1267,6 +1269,27 @@ func (a *agent) isClosed() bool {
12671269
}
12681270
}
12691271

1272+
func (a *agent) HTTPDebug() http.Handler {
1273+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1274+
a.closeMutex.Lock()
1275+
network := a.network
1276+
a.closeMutex.Unlock()
1277+
1278+
if network == nil {
1279+
w.WriteHeader(http.StatusOK)
1280+
_, _ = w.Write([]byte("network is not ready yet"))
1281+
return
1282+
}
1283+
1284+
if r.URL.Path == "/debug/magicsock" {
1285+
network.MagicsockServeHTTPDebug(w, r)
1286+
} else {
1287+
w.WriteHeader(http.StatusNotFound)
1288+
_, _ = w.Write([]byte("404 not found"))
1289+
}
1290+
})
1291+
}
1292+
12701293
func (a *agent) Close() error {
12711294
a.closeMutex.Lock()
12721295
defer a.closeMutex.Unlock()

cli/agent.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
3838
sshMaxTimeout time.Duration
3939
tailnetListenPort int64
4040
prometheusAddress string
41+
debugAddress string
4142
)
4243
cmd := &clibase.Cmd{
4344
Use: "agent",
@@ -48,7 +49,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
4849
ctx, cancel := context.WithCancel(inv.Context())
4950
defer cancel()
5051

51-
agentPorts := map[int]string{}
52+
ignorePorts := map[int]string{}
5253

5354
isLinux := runtime.GOOS == "linux"
5455

@@ -125,14 +126,14 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
125126
defer pprofSrvClose()
126127
// Do a best effort here. If this fails, it's not a big deal.
127128
if port, err := urlPort(pprofAddress); err == nil {
128-
agentPorts[port] = "pprof"
129+
ignorePorts[port] = "pprof"
129130
}
130131

131132
prometheusSrvClose := ServeHandler(ctx, logger, prometheusMetricsHandler(), prometheusAddress, "prometheus")
132133
defer prometheusSrvClose()
133134
// Do a best effort here. If this fails, it's not a big deal.
134135
if port, err := urlPort(prometheusAddress); err == nil {
135-
agentPorts[port] = "prometheus"
136+
ignorePorts[port] = "prometheus"
136137
}
137138

138139
// exchangeToken returns a session token.
@@ -196,7 +197,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
196197
return xerrors.Errorf("add executable to $PATH: %w", err)
197198
}
198199

199-
closer := agent.New(agent.Options{
200+
agnt := agent.New(agent.Options{
200201
Client: client,
201202
Logger: logger,
202203
LogDir: logDir,
@@ -215,11 +216,19 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
215216
EnvironmentVariables: map[string]string{
216217
"GIT_ASKPASS": executablePath,
217218
},
218-
AgentPorts: agentPorts,
219+
IgnorePorts: ignorePorts,
219220
SSHMaxTimeout: sshMaxTimeout,
220221
})
222+
223+
debugSrvClose := ServeHandler(ctx, logger, agnt.HTTPDebug(), debugAddress, "debug")
224+
defer debugSrvClose()
225+
// Do a best effort here. If this fails, it's not a big deal.
226+
if port, err := urlPort(debugAddress); err == nil {
227+
ignorePorts[port] = "debug"
228+
}
229+
221230
<-ctx.Done()
222-
return closer.Close()
231+
return agnt.Close()
223232
},
224233
}
225234

@@ -273,6 +282,13 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
273282
Value: clibase.StringOf(&prometheusAddress),
274283
Description: "The bind address to serve Prometheus metrics.",
275284
},
285+
{
286+
Flag: "debug-address",
287+
Default: "127.0.0.1:2113",
288+
Env: "CODER_AGENT_DEBUG_ADDRESS",
289+
Value: clibase.StringOf(&debugAddress),
290+
Description: "The bind address to serve a debug HTTP server.",
291+
},
276292
}
277293

278294
return cmd

cli/testdata/coder_agent_--help.golden

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ Starts the Coder workspace agent.
66
--auth string, $CODER_AGENT_AUTH (default: token)
77
Specify the authentication type to use for the agent.
88

9+
--debug-address string, $CODER_AGENT_DEBUG_ADDRESS (default: 127.0.0.1:2113)
10+
The bind address to serve a debug HTTP server.
11+
912
--log-dir string, $CODER_AGENT_LOG_DIR (default: /tmp)
1013
Specify the location for the agent log files.
1114

coderd/apidoc/docs.go

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,8 @@ func New(options *Options) *API {
250250
if options.HealthcheckFunc == nil {
251251
options.HealthcheckFunc = func(ctx context.Context) (*healthcheck.Report, error) {
252252
return healthcheck.Run(ctx, &healthcheck.ReportOptions{
253-
DERPMap: options.DERPMap.Clone(),
253+
AccessURL: options.AccessURL,
254+
DERPMap: options.DERPMap.Clone(),
254255
})
255256
}
256257
}

coderd/healthcheck/accessurl.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type AccessURLReport struct {
1515
Reachable bool
1616
StatusCode int
1717
HealthzResponse string
18-
Err error
18+
Error error
1919
}
2020

2121
type AccessURLOptions struct {
@@ -27,32 +27,37 @@ func (r *AccessURLReport) Run(ctx context.Context, opts *AccessURLOptions) {
2727
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
2828
defer cancel()
2929

30+
if opts.AccessURL == nil {
31+
r.Error = xerrors.New("access URL is nil")
32+
return
33+
}
34+
3035
if opts.Client == nil {
3136
opts.Client = http.DefaultClient
3237
}
3338

3439
accessURL, err := opts.AccessURL.Parse("/healthz")
3540
if err != nil {
36-
r.Err = xerrors.Errorf("parse healthz endpoint: %w", err)
41+
r.Error = xerrors.Errorf("parse healthz endpoint: %w", err)
3742
return
3843
}
3944

4045
req, err := http.NewRequestWithContext(ctx, "GET", accessURL.String(), nil)
4146
if err != nil {
42-
r.Err = xerrors.Errorf("create healthz request: %w", err)
47+
r.Error = xerrors.Errorf("create healthz request: %w", err)
4348
return
4449
}
4550

4651
res, err := opts.Client.Do(req)
4752
if err != nil {
48-
r.Err = xerrors.Errorf("get healthz endpoint: %w", err)
53+
r.Error = xerrors.Errorf("get healthz endpoint: %w", err)
4954
return
5055
}
5156
defer res.Body.Close()
5257

5358
body, err := io.ReadAll(res.Body)
5459
if err != nil {
55-
r.Err = xerrors.Errorf("read healthz response: %w", err)
60+
r.Error = xerrors.Errorf("read healthz response: %w", err)
5661
return
5762
}
5863

coderd/healthcheck/accessurl_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func TestAccessURL(t *testing.T) {
3636
assert.True(t, report.Reachable)
3737
assert.Equal(t, http.StatusOK, report.StatusCode)
3838
assert.Equal(t, "OK", report.HealthzResponse)
39-
assert.NoError(t, report.Err)
39+
assert.NoError(t, report.Error)
4040
})
4141

4242
t.Run("404", func(t *testing.T) {
@@ -66,7 +66,7 @@ func TestAccessURL(t *testing.T) {
6666
assert.True(t, report.Reachable)
6767
assert.Equal(t, http.StatusNotFound, report.StatusCode)
6868
assert.Equal(t, string(resp), report.HealthzResponse)
69-
assert.NoError(t, report.Err)
69+
assert.NoError(t, report.Error)
7070
})
7171

7272
t.Run("ClientErr", func(t *testing.T) {
@@ -102,7 +102,7 @@ func TestAccessURL(t *testing.T) {
102102
assert.False(t, report.Reachable)
103103
assert.Equal(t, 0, report.StatusCode)
104104
assert.Equal(t, "", report.HealthzResponse)
105-
assert.ErrorIs(t, report.Err, expErr)
105+
assert.ErrorIs(t, report.Error, expErr)
106106
})
107107
}
108108

coderd/healthcheck/derp.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ type DERPReport struct {
3333
Netcheck *netcheck.Report `json:"netcheck"`
3434
NetcheckErr error `json:"netcheck_err"`
3535
NetcheckLogs []string `json:"netcheck_logs"`
36+
37+
Error error `json:"error"`
3638
}
3739

3840
type DERPRegionReport struct {
@@ -41,6 +43,7 @@ type DERPRegionReport struct {
4143

4244
Region *tailcfg.DERPRegion `json:"region"`
4345
NodeReports []*DERPNodeReport `json:"node_reports"`
46+
Error error `json:"error"`
4447
}
4548
type DERPNodeReport struct {
4649
mu sync.Mutex
@@ -55,6 +58,7 @@ type DERPNodeReport struct {
5558
UsesWebsocket bool `json:"uses_websocket"`
5659
ClientLogs [][]string `json:"client_logs"`
5760
ClientErrs [][]error `json:"client_errs"`
61+
Error error `json:"error"`
5862

5963
STUN DERPStunReport `json:"stun"`
6064
}
@@ -77,12 +81,19 @@ func (r *DERPReport) Run(ctx context.Context, opts *DERPReportOptions) {
7781

7882
wg.Add(len(opts.DERPMap.Regions))
7983
for _, region := range opts.DERPMap.Regions {
80-
region := region
81-
go func() {
82-
defer wg.Done()
83-
regionReport := DERPRegionReport{
84+
var (
85+
region = region
86+
regionReport = DERPRegionReport{
8487
Region: region,
8588
}
89+
)
90+
go func() {
91+
defer wg.Done()
92+
defer func() {
93+
if err := recover(); err != nil {
94+
regionReport.Error = xerrors.Errorf("%v", err)
95+
}
96+
}()
8697

8798
regionReport.Run(ctx)
8899

@@ -117,14 +128,21 @@ func (r *DERPRegionReport) Run(ctx context.Context) {
117128

118129
wg.Add(len(r.Region.Nodes))
119130
for _, node := range r.Region.Nodes {
120-
node := node
121-
go func() {
122-
defer wg.Done()
123-
124-
nodeReport := DERPNodeReport{
131+
var (
132+
node = node
133+
nodeReport = DERPNodeReport{
125134
Node: node,
126135
Healthy: true,
127136
}
137+
)
138+
139+
go func() {
140+
defer wg.Done()
141+
defer func() {
142+
if err := recover(); err != nil {
143+
nodeReport.Error = xerrors.Errorf("%v", err)
144+
}
145+
}()
128146

129147
nodeReport.Run(ctx)
130148

0 commit comments

Comments
 (0)