@@ -13,6 +13,7 @@ import (
1313
1414 "github.com/cenkalti/backoff/v4"
1515 "github.com/google/uuid"
16+ "golang.org/x/exp/slices"
1617 "golang.org/x/xerrors"
1718 "nhooyr.io/websocket"
1819
@@ -307,6 +308,9 @@ type binding struct {
307308 node * agpl.Node
308309}
309310
311+ func (b * binding ) isAgent () bool { return b .client == uuid .Nil }
312+ func (b * binding ) isClient () bool { return b .client != uuid .Nil }
313+
310314// binder reads node bindings from the channel and writes them to the database. It handles retries with a backoff.
311315type binder struct {
312316 ctx context.Context
@@ -386,19 +390,19 @@ func (b *binder) writeOne(bnd binding) error {
386390 if err != nil {
387391 // this is very bad news, but it should never happen because the node was Unmarshalled by this process
388392 // earlier.
389- b .logger .Error (b .ctx , "failed to marshall node" , slog .Error (err ))
393+ b .logger .Error (b .ctx , "failed to marshal node" , slog .Error (err ))
390394 return err
391395 }
392396 }
393397
394398 switch {
395- case bnd .client == uuid . Nil && len (nodeRaw ) > 0 :
399+ case bnd .isAgent () && len (nodeRaw ) > 0 :
396400 _ , err = b .store .UpsertTailnetAgent (b .ctx , database.UpsertTailnetAgentParams {
397401 ID : bnd .agent ,
398402 CoordinatorID : b .coordinatorID ,
399403 Node : nodeRaw ,
400404 })
401- case bnd .client == uuid . Nil && len (nodeRaw ) == 0 :
405+ case bnd .isAgent () && len (nodeRaw ) == 0 :
402406 _ , err = b .store .DeleteTailnetAgent (b .ctx , database.DeleteTailnetAgentParams {
403407 ID : bnd .agent ,
404408 CoordinatorID : b .coordinatorID ,
@@ -407,14 +411,14 @@ func (b *binder) writeOne(bnd binding) error {
407411 // treat deletes as idempotent
408412 err = nil
409413 }
410- case bnd .client != uuid . Nil && len (nodeRaw ) > 0 :
414+ case bnd .isClient () && len (nodeRaw ) > 0 :
411415 _ , err = b .store .UpsertTailnetClient (b .ctx , database.UpsertTailnetClientParams {
412416 ID : bnd .client ,
413417 CoordinatorID : b .coordinatorID ,
414418 AgentID : bnd .agent ,
415419 Node : nodeRaw ,
416420 })
417- case bnd .client != uuid . Nil && len (nodeRaw ) == 0 :
421+ case bnd .isClient () && len (nodeRaw ) == 0 :
418422 _ , err = b .store .DeleteTailnetClient (b .ctx , database.DeleteTailnetClientParams {
419423 ID : bnd .client ,
420424 CoordinatorID : b .coordinatorID ,
@@ -927,6 +931,27 @@ func (q *querier) updateAll() {
927931 }
928932}
929933
934+ func (q * querier ) getAll (ctx context.Context ) (map [uuid.UUID ]database.TailnetAgent , map [uuid.UUID ][]database.TailnetClient , error ) {
935+ agents , err := q .store .GetAllTailnetAgents (ctx )
936+ if err != nil {
937+ return nil , nil , xerrors .Errorf ("get all tailnet agents: %w" , err )
938+ }
939+ agentsMap := map [uuid.UUID ]database.TailnetAgent {}
940+ for _ , agent := range agents {
941+ agentsMap [agent .ID ] = agent
942+ }
943+ clients , err := q .store .GetAllTailnetClients (ctx )
944+ if err != nil {
945+ return nil , nil , xerrors .Errorf ("get all tailnet clients: %w" , err )
946+ }
947+ clientsMap := map [uuid.UUID ][]database.TailnetClient {}
948+ for _ , client := range clients {
949+ clientsMap [client .AgentID ] = append (clientsMap [client .AgentID ], client )
950+ }
951+
952+ return agentsMap , clientsMap , nil
953+ }
954+
930955func parseClientUpdate (msg string ) (client , agent uuid.UUID , err error ) {
931956 parts := strings .Split (msg , "," )
932957 if len (parts ) != 2 {
@@ -1289,8 +1314,90 @@ func (h *heartbeats) cleanup() {
12891314 h .logger .Debug (h .ctx , "cleaned up old coordinators" )
12901315}
12911316
1292- func (* pgCoord ) ServeHTTPDebug (w http.ResponseWriter , _ * http.Request ) {
1293- // TODO(spikecurtis) I'd like to hold off implementing this until after the rest of this is code reviewed.
1294- w .WriteHeader (http .StatusOK )
1295- _ , _ = w .Write ([]byte ("Coder Enterprise PostgreSQL distributed tailnet coordinator" ))
1317+ func (c * pgCoord ) ServeHTTPDebug (w http.ResponseWriter , r * http.Request ) {
1318+ ctx := r .Context ()
1319+ debug , err := c .htmlDebug (ctx )
1320+ if err != nil {
1321+ w .WriteHeader (http .StatusInternalServerError )
1322+ _ , _ = w .Write ([]byte (err .Error ()))
1323+ return
1324+ }
1325+
1326+ agpl .CoordinatorHTTPDebug (debug )(w , r )
1327+ }
1328+
1329+ func (c * pgCoord ) htmlDebug (ctx context.Context ) (agpl.HTMLDebug , error ) {
1330+ now := time .Now ()
1331+ data := agpl.HTMLDebug {}
1332+ agents , clients , err := c .querier .getAll (ctx )
1333+ if err != nil {
1334+ return data , xerrors .Errorf ("get all agents and clients: %w" , err )
1335+ }
1336+
1337+ for _ , agent := range agents {
1338+ htmlAgent := & agpl.HTMLAgent {
1339+ ID : agent .ID ,
1340+ // Name: ??, TODO: get agent names
1341+ LastWriteAge : now .Sub (agent .UpdatedAt ).Round (time .Second ),
1342+ }
1343+ for _ , conn := range clients [agent .ID ] {
1344+ htmlAgent .Connections = append (htmlAgent .Connections , & agpl.HTMLClient {
1345+ ID : conn .ID ,
1346+ Name : conn .ID .String (),
1347+ LastWriteAge : now .Sub (conn .UpdatedAt ).Round (time .Second ),
1348+ })
1349+ data .Nodes = append (data .Nodes , & agpl.HTMLNode {
1350+ ID : conn .ID ,
1351+ Node : conn .Node ,
1352+ })
1353+ }
1354+ slices .SortFunc (htmlAgent .Connections , func (a , b * agpl.HTMLClient ) bool {
1355+ return a .Name < b .Name
1356+ })
1357+
1358+ data .Agents = append (data .Agents , htmlAgent )
1359+ data .Nodes = append (data .Nodes , & agpl.HTMLNode {
1360+ ID : agent .ID ,
1361+ // Name: ??, TODO: get agent names
1362+ Node : agent .Node ,
1363+ })
1364+ }
1365+ slices .SortFunc (data .Agents , func (a , b * agpl.HTMLAgent ) bool {
1366+ return a .Name < b .Name
1367+ })
1368+
1369+ for agentID , conns := range clients {
1370+ if len (conns ) == 0 {
1371+ continue
1372+ }
1373+
1374+ if _ , ok := agents [agentID ]; ok {
1375+ continue
1376+ }
1377+ agent := & agpl.HTMLAgent {
1378+ Name : "unknown" ,
1379+ ID : agentID ,
1380+ }
1381+ for _ , conn := range conns {
1382+ agent .Connections = append (agent .Connections , & agpl.HTMLClient {
1383+ Name : conn .ID .String (),
1384+ ID : conn .ID ,
1385+ LastWriteAge : now .Sub (conn .UpdatedAt ).Round (time .Second ),
1386+ })
1387+ data .Nodes = append (data .Nodes , & agpl.HTMLNode {
1388+ ID : conn .ID ,
1389+ Node : conn .Node ,
1390+ })
1391+ }
1392+ slices .SortFunc (agent .Connections , func (a , b * agpl.HTMLClient ) bool {
1393+ return a .Name < b .Name
1394+ })
1395+
1396+ data .MissingAgents = append (data .MissingAgents , agent )
1397+ }
1398+ slices .SortFunc (data .MissingAgents , func (a , b * agpl.HTMLAgent ) bool {
1399+ return a .Name < b .Name
1400+ })
1401+
1402+ return data , nil
12961403}
0 commit comments