feat(support): add client magicsock and agent prometheus metrics to support bundle (#12604)

* feat(codersdk): add ability to fetch prometheus metrics directly from agent
* feat(support): add client magicsock and agent prometheus metrics to support bundle
* refactor(support): simplify AgentInfo control flow

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
This commit is contained in:
Cian Johnston
2024-03-15 15:33:49 +00:00
committed by GitHub
co-authored by Mathias Fredriksson
parent 4d9e6c0134
commit b0c4e7504c
8 changed files with 187 additions and 108 deletions
+117 -79
View File
@@ -7,6 +7,7 @@ import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"golang.org/x/sync/errgroup"
@@ -57,14 +58,16 @@ type Workspace struct {
}
type Agent struct {
Agent *codersdk.WorkspaceAgent `json:"agent"`
ListeningPorts *codersdk.WorkspaceAgentListeningPortsResponse `json:"listening_ports"`
Logs []byte `json:"logs"`
MagicsockHTML []byte `json:"magicsock_html"`
Manifest *agentsdk.Manifest `json:"manifest"`
PeerDiagnostics *tailnet.PeerDiagnostics `json:"peer_diagnostics"`
PingResult *ipnstate.PingResult `json:"ping_result"`
StartupLogs []codersdk.WorkspaceAgentLog `json:"startup_logs"`
Agent *codersdk.WorkspaceAgent `json:"agent"`
ListeningPorts *codersdk.WorkspaceAgentListeningPortsResponse `json:"listening_ports"`
Logs []byte `json:"logs"`
ClientMagicsockHTML []byte `json:"client_magicsock_html"`
AgentMagicsockHTML []byte `json:"agent_magicsock_html"`
Manifest *agentsdk.Manifest `json:"manifest"`
PeerDiagnostics *tailnet.PeerDiagnostics `json:"peer_diagnostics"`
PingResult *ipnstate.PingResult `json:"ping_result"`
Prometheus []byte `json:"prometheus"`
StartupLogs []codersdk.WorkspaceAgentLog `json:"startup_logs"`
}
// Deps is a set of dependencies for discovering information
@@ -313,77 +316,10 @@ func AgentInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, ag
return nil
})
conn, err := client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{
Logger: log.Named("dial-agent"),
BlockEndpoints: false,
})
if err != nil {
log.Error(ctx, "dial agent", slog.Error(err))
} else {
defer func() {
if err := conn.Close(); err != nil {
log.Error(ctx, "failed to close agent connection", slog.Error(err))
}
<-conn.Closed()
}()
if !conn.AwaitReachable(ctx) {
log.Error(ctx, "timed out waiting for agent")
} else {
eg.Go(func() error {
_, _, pingRes, err := conn.Ping(ctx)
if err != nil {
return xerrors.Errorf("ping agent: %w", err)
}
a.PingResult = pingRes
return nil
})
eg.Go(func() error {
pds := conn.GetPeerDiagnostics()
a.PeerDiagnostics = &pds
return nil
})
eg.Go(func() error {
msBytes, err := conn.DebugMagicsock(ctx)
if err != nil {
return xerrors.Errorf("get agent magicsock page: %w", err)
}
a.MagicsockHTML = msBytes
return nil
})
eg.Go(func() error {
manifestRes, err := conn.DebugManifest(ctx)
if err != nil {
return xerrors.Errorf("fetch manifest: %w", err)
}
if err := json.NewDecoder(bytes.NewReader(manifestRes)).Decode(&a.Manifest); err != nil {
return xerrors.Errorf("decode agent manifest: %w", err)
}
return nil
})
eg.Go(func() error {
logBytes, err := conn.DebugLogs(ctx)
if err != nil {
return xerrors.Errorf("fetch coder agent logs: %w", err)
}
a.Logs = logBytes
return nil
})
eg.Go(func() error {
lps, err := conn.ListeningPorts(ctx)
if err != nil {
return xerrors.Errorf("get listening ports: %w", err)
}
a.ListeningPorts = &lps
return nil
})
}
}
// to simplify control flow, fetching information directly from
// the agent is handled in a separate function
closer := connectedAgentInfo(ctx, client, log, agentID, &eg, &a)
defer closer()
if err := eg.Wait(); err != nil {
log.Error(ctx, "fetch agent information", slog.Error(err))
@@ -392,6 +328,108 @@ func AgentInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, ag
return a
}
func connectedAgentInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, agentID uuid.UUID, eg *errgroup.Group, a *Agent) (closer func()) {
conn, err := client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{
Logger: log.Named("dial-agent"),
BlockEndpoints: false,
})
closer = func() {}
if err != nil {
log.Error(ctx, "dial agent", slog.Error(err))
return closer
}
if !conn.AwaitReachable(ctx) {
log.Error(ctx, "timed out waiting for agent")
return closer
}
closer = func() {
if err := conn.Close(); err != nil {
log.Error(ctx, "failed to close agent connection", slog.Error(err))
}
<-conn.Closed()
}
eg.Go(func() error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost/", nil)
if err != nil {
return xerrors.Errorf("create request: %w", err)
}
rr := httptest.NewRecorder()
conn.MagicsockServeHTTPDebug(rr, req)
a.ClientMagicsockHTML = rr.Body.Bytes()
return nil
})
eg.Go(func() error {
promRes, err := conn.PrometheusMetrics(ctx)
if err != nil {
return xerrors.Errorf("fetch agent prometheus metrics: %w", err)
}
a.Prometheus = promRes
return nil
})
eg.Go(func() error {
_, _, pingRes, err := conn.Ping(ctx)
if err != nil {
return xerrors.Errorf("ping agent: %w", err)
}
a.PingResult = pingRes
return nil
})
eg.Go(func() error {
pds := conn.GetPeerDiagnostics()
a.PeerDiagnostics = &pds
return nil
})
eg.Go(func() error {
msBytes, err := conn.DebugMagicsock(ctx)
if err != nil {
return xerrors.Errorf("get agent magicsock page: %w", err)
}
a.AgentMagicsockHTML = msBytes
return nil
})
eg.Go(func() error {
manifestRes, err := conn.DebugManifest(ctx)
if err != nil {
return xerrors.Errorf("fetch manifest: %w", err)
}
if err := json.NewDecoder(bytes.NewReader(manifestRes)).Decode(&a.Manifest); err != nil {
return xerrors.Errorf("decode agent manifest: %w", err)
}
return nil
})
eg.Go(func() error {
logBytes, err := conn.DebugLogs(ctx)
if err != nil {
return xerrors.Errorf("fetch coder agent logs: %w", err)
}
a.Logs = logBytes
return nil
})
eg.Go(func() error {
lps, err := conn.ListeningPorts(ctx)
if err != nil {
return xerrors.Errorf("get listening ports: %w", err)
}
a.ListeningPorts = &lps
return nil
})
return closer
}
// Run generates a support bundle with the given dependencies.
func Run(ctx context.Context, d *Deps) (*Bundle, error) {
var b Bundle
+3 -1
View File
@@ -75,9 +75,11 @@ func TestRun(t *testing.T) {
assertNotNilNotEmpty(t, bun.Agent.Agent, "agent should be present")
assertNotNilNotEmpty(t, bun.Agent.ListeningPorts, "agent listening ports should be present")
assertNotNilNotEmpty(t, bun.Agent.Logs, "agent logs should be present")
assertNotNilNotEmpty(t, bun.Agent.MagicsockHTML, "agent magicsock should be present")
assertNotNilNotEmpty(t, bun.Agent.AgentMagicsockHTML, "agent magicsock should be present")
assertNotNilNotEmpty(t, bun.Agent.ClientMagicsockHTML, "client magicsock should be present")
assertNotNilNotEmpty(t, bun.Agent.PeerDiagnostics, "agent peer diagnostics should be present")
assertNotNilNotEmpty(t, bun.Agent.PingResult, "agent ping result should be present")
assertNotNilNotEmpty(t, bun.Agent.Prometheus, "agent prometheus metrics should be present")
assertNotNilNotEmpty(t, bun.Agent.StartupLogs, "agent startup logs should be present")
assertNotNilNotEmpty(t, bun.Logs, "bundle logs should be present")
})