@@ -13,6 +13,7 @@ import (
13
13
14
14
"github.com/cenkalti/backoff/v4"
15
15
"github.com/google/uuid"
16
+ "golang.org/x/exp/slices"
16
17
"golang.org/x/xerrors"
17
18
"nhooyr.io/websocket"
18
19
@@ -307,6 +308,9 @@ type binding struct {
307
308
node * agpl.Node
308
309
}
309
310
311
+ func (b * binding ) isAgent () bool { return b .client == uuid .Nil }
312
+ func (b * binding ) isClient () bool { return b .client != uuid .Nil }
313
+
310
314
// binder reads node bindings from the channel and writes them to the database. It handles retries with a backoff.
311
315
type binder struct {
312
316
ctx context.Context
@@ -386,19 +390,19 @@ func (b *binder) writeOne(bnd binding) error {
386
390
if err != nil {
387
391
// this is very bad news, but it should never happen because the node was Unmarshalled by this process
388
392
// 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 ))
390
394
return err
391
395
}
392
396
}
393
397
394
398
switch {
395
- case bnd .client == uuid . Nil && len (nodeRaw ) > 0 :
399
+ case bnd .isAgent () && len (nodeRaw ) > 0 :
396
400
_ , err = b .store .UpsertTailnetAgent (b .ctx , database.UpsertTailnetAgentParams {
397
401
ID : bnd .agent ,
398
402
CoordinatorID : b .coordinatorID ,
399
403
Node : nodeRaw ,
400
404
})
401
- case bnd .client == uuid . Nil && len (nodeRaw ) == 0 :
405
+ case bnd .isAgent () && len (nodeRaw ) == 0 :
402
406
_ , err = b .store .DeleteTailnetAgent (b .ctx , database.DeleteTailnetAgentParams {
403
407
ID : bnd .agent ,
404
408
CoordinatorID : b .coordinatorID ,
@@ -407,14 +411,14 @@ func (b *binder) writeOne(bnd binding) error {
407
411
// treat deletes as idempotent
408
412
err = nil
409
413
}
410
- case bnd .client != uuid . Nil && len (nodeRaw ) > 0 :
414
+ case bnd .isClient () && len (nodeRaw ) > 0 :
411
415
_ , err = b .store .UpsertTailnetClient (b .ctx , database.UpsertTailnetClientParams {
412
416
ID : bnd .client ,
413
417
CoordinatorID : b .coordinatorID ,
414
418
AgentID : bnd .agent ,
415
419
Node : nodeRaw ,
416
420
})
417
- case bnd .client != uuid . Nil && len (nodeRaw ) == 0 :
421
+ case bnd .isClient () && len (nodeRaw ) == 0 :
418
422
_ , err = b .store .DeleteTailnetClient (b .ctx , database.DeleteTailnetClientParams {
419
423
ID : bnd .client ,
420
424
CoordinatorID : b .coordinatorID ,
@@ -927,6 +931,27 @@ func (q *querier) updateAll() {
927
931
}
928
932
}
929
933
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
+
930
955
func parseClientUpdate (msg string ) (client , agent uuid.UUID , err error ) {
931
956
parts := strings .Split (msg , "," )
932
957
if len (parts ) != 2 {
@@ -1289,8 +1314,90 @@ func (h *heartbeats) cleanup() {
1289
1314
h .logger .Debug (h .ctx , "cleaned up old coordinators" )
1290
1315
}
1291
1316
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
1296
1403
}
0 commit comments