@@ -29,6 +29,9 @@ import (
29
29
30
30
"cdr.dev/slog"
31
31
"cdr.dev/slog/sloggers/slogtest"
32
+ "github.com/coder/quartz"
33
+ "github.com/coder/websocket"
34
+
32
35
"github.com/coder/coder/v2/agent"
33
36
"github.com/coder/coder/v2/agent/agentcontainers"
34
37
"github.com/coder/coder/v2/agent/agentcontainers/acmock"
@@ -47,6 +50,7 @@ import (
47
50
"github.com/coder/coder/v2/coderd/externalauth"
48
51
"github.com/coder/coder/v2/coderd/jwtutils"
49
52
"github.com/coder/coder/v2/coderd/rbac"
53
+ "github.com/coder/coder/v2/coderd/telemetry"
50
54
"github.com/coder/coder/v2/codersdk"
51
55
"github.com/coder/coder/v2/codersdk/agentsdk"
52
56
"github.com/coder/coder/v2/codersdk/workspacesdk"
@@ -56,8 +60,6 @@ import (
56
60
tailnetproto "github.com/coder/coder/v2/tailnet/proto"
57
61
"github.com/coder/coder/v2/tailnet/tailnettest"
58
62
"github.com/coder/coder/v2/testutil"
59
- "github.com/coder/quartz"
60
- "github.com/coder/websocket"
61
63
)
62
64
63
65
func TestWorkspaceAgent (t * testing.T ) {
@@ -2133,35 +2135,53 @@ func TestOwnedWorkspacesCoordinate(t *testing.T) {
2133
2135
2134
2136
ctx := testutil .Context (t , testutil .WaitLong )
2135
2137
logger := testutil .Logger (t )
2138
+
2139
+ fTelemetry := newFakeTelemetryReporter (ctx , t , 200 )
2140
+ fTelemetry .enabled = false
2136
2141
firstClient , _ , api := coderdtest .NewWithAPI (t , & coderdtest.Options {
2137
- Coordinator : tailnet .NewCoordinator (logger ),
2142
+ Coordinator : tailnet .NewCoordinator (logger ),
2143
+ TelemetryReporter : fTelemetry ,
2138
2144
})
2139
2145
firstUser := coderdtest .CreateFirstUser (t , firstClient )
2140
2146
member , memberUser := coderdtest .CreateAnotherUser (t , firstClient , firstUser .OrganizationID , rbac .RoleTemplateAdmin ())
2141
2147
2142
2148
// Create a workspace with an agent
2143
2149
firstWorkspace := buildWorkspaceWithAgent (t , member , firstUser .OrganizationID , memberUser .ID , api .Database , api .Pubsub )
2144
2150
2151
+ // enable telemetry now that workspace is built; we don't care about snapshots before this.
2152
+ fTelemetry .enabled = true
2153
+
2145
2154
u , err := member .URL .Parse ("/api/v2/tailnet" )
2146
2155
require .NoError (t , err )
2147
2156
q := u .Query ()
2148
2157
q .Set ("version" , "2.0" )
2149
2158
u .RawQuery = q .Encode ()
2150
2159
2160
+ predialTime := time .Now ()
2161
+
2151
2162
//nolint:bodyclose // websocket package closes this for you
2152
2163
wsConn , resp , err := websocket .Dial (ctx , u .String (), & websocket.DialOptions {
2153
2164
HTTPHeader : http.Header {
2154
2165
"Coder-Session-Token" : []string {member .SessionToken ()},
2155
2166
},
2156
2167
})
2157
2168
if err != nil {
2158
- if resp .StatusCode != http .StatusSwitchingProtocols {
2169
+ if resp != nil && resp .StatusCode != http .StatusSwitchingProtocols {
2159
2170
err = codersdk .ReadBodyAsError (resp )
2160
2171
}
2161
2172
require .NoError (t , err )
2162
2173
}
2163
2174
defer wsConn .Close (websocket .StatusNormalClosure , "done" )
2164
2175
2176
+ // Check telemetry
2177
+ snapshot := testutil .RequireRecvCtx (ctx , t , fTelemetry .snapshots )
2178
+ require .Len (t , snapshot .UserTailnetConnections , 1 )
2179
+ telemetryConnection := snapshot .UserTailnetConnections [0 ]
2180
+ require .Equal (t , memberUser .ID .String (), telemetryConnection .UserID )
2181
+ require .GreaterOrEqual (t , telemetryConnection .ConnectedAt , predialTime )
2182
+ require .LessOrEqual (t , telemetryConnection .ConnectedAt , time .Now ())
2183
+ require .NotEmpty (t , telemetryConnection .PeerID )
2184
+
2165
2185
rpcClient , err := tailnet .NewDRPCClient (
2166
2186
websocket .NetConn (ctx , wsConn , websocket .MessageBinary ),
2167
2187
logger ,
@@ -2209,6 +2229,23 @@ func TestOwnedWorkspacesCoordinate(t *testing.T) {
2209
2229
NumAgents : 0 ,
2210
2230
},
2211
2231
})
2232
+ err = stream .Close ()
2233
+ require .NoError (t , err )
2234
+
2235
+ beforeDisconnectTime := time .Now ()
2236
+ err = wsConn .Close (websocket .StatusNormalClosure , "done" )
2237
+ require .NoError (t , err )
2238
+
2239
+ snapshot = testutil .RequireRecvCtx (ctx , t , fTelemetry .snapshots )
2240
+ require .Len (t , snapshot .UserTailnetConnections , 1 )
2241
+ telemetryDisconnection := snapshot .UserTailnetConnections [0 ]
2242
+ require .Equal (t , memberUser .ID .String (), telemetryDisconnection .UserID )
2243
+ require .Equal (t , telemetryConnection .ConnectedAt , telemetryDisconnection .ConnectedAt )
2244
+ require .Equal (t , telemetryConnection .UserID , telemetryDisconnection .UserID )
2245
+ require .Equal (t , telemetryConnection .PeerID , telemetryDisconnection .PeerID )
2246
+ require .NotNil (t , telemetryDisconnection .DisconnectedAt )
2247
+ require .GreaterOrEqual (t , * telemetryDisconnection .DisconnectedAt , beforeDisconnectTime )
2248
+ require .LessOrEqual (t , * telemetryDisconnection .DisconnectedAt , time .Now ())
2212
2249
}
2213
2250
2214
2251
func buildWorkspaceWithAgent (
@@ -2334,3 +2371,46 @@ func waitForUpdates(
2334
2371
t .Fatal ("Timeout waiting for desired state" , currentState )
2335
2372
}
2336
2373
}
2374
+
2375
+ // fakeTelemetryReporter is a fake implementation of telemetry.Reporter
2376
+ // that sends snapshots on a buffered channel, useful for testing.
2377
+ type fakeTelemetryReporter struct {
2378
+ enabled bool
2379
+ snapshots chan * telemetry.Snapshot
2380
+ t testing.TB
2381
+ ctx context.Context
2382
+ }
2383
+
2384
+ // newFakeTelemetryReporter creates a new fakeTelemetryReporter with a buffered channel.
2385
+ // The buffer size determines how many snapshots can be reported before blocking.
2386
+ func newFakeTelemetryReporter (ctx context.Context , t testing.TB , bufferSize int ) * fakeTelemetryReporter {
2387
+ return & fakeTelemetryReporter {
2388
+ enabled : true ,
2389
+ snapshots : make (chan * telemetry.Snapshot , bufferSize ),
2390
+ ctx : ctx ,
2391
+ t : t ,
2392
+ }
2393
+ }
2394
+
2395
+ // Report implements the telemetry.Reporter interface by sending the snapshot
2396
+ // to the snapshots channel.
2397
+ func (f * fakeTelemetryReporter ) Report (snapshot * telemetry.Snapshot ) {
2398
+ if ! f .enabled {
2399
+ return
2400
+ }
2401
+
2402
+ select {
2403
+ case f .snapshots <- snapshot :
2404
+ // Successfully sent
2405
+ case <- f .ctx .Done ():
2406
+ f .t .Error ("context closed while writing snapshot" )
2407
+ }
2408
+ }
2409
+
2410
+ // Enabled implements the telemetry.Reporter interface.
2411
+ func (f * fakeTelemetryReporter ) Enabled () bool {
2412
+ return f .enabled
2413
+ }
2414
+
2415
+ // Close implements the telemetry.Reporter interface.
2416
+ func (* fakeTelemetryReporter ) Close () {}
0 commit comments