7
7
"net/http/httputil"
8
8
"net/url"
9
9
"testing"
10
+ "time"
10
11
11
12
"github.com/google/uuid"
12
13
"github.com/moby/moby/pkg/namesgenerator"
@@ -16,7 +17,9 @@ import (
16
17
"cdr.dev/slog"
17
18
"cdr.dev/slog/sloggers/slogtest"
18
19
"github.com/coder/coder/agent"
20
+ "github.com/coder/coder/buildinfo"
19
21
"github.com/coder/coder/coderd/coderdtest"
22
+ "github.com/coder/coder/coderd/database"
20
23
"github.com/coder/coder/coderd/database/dbtestutil"
21
24
"github.com/coder/coder/coderd/workspaceapps"
22
25
"github.com/coder/coder/codersdk"
@@ -245,6 +248,341 @@ func TestWorkspaceProxyCRUD(t *testing.T) {
245
248
})
246
249
}
247
250
251
+ func TestProxyRegisterDeregister (t * testing.T ) {
252
+ t .Parallel ()
253
+
254
+ setup := func (t * testing.T ) (* codersdk.Client , database.Store ) {
255
+ dv := coderdtest .DeploymentValues (t )
256
+ dv .Experiments = []string {
257
+ string (codersdk .ExperimentMoons ),
258
+ "*" ,
259
+ }
260
+
261
+ db , pubsub := dbtestutil .NewDB (t )
262
+ client := coderdenttest .New (t , & coderdenttest.Options {
263
+ Options : & coderdtest.Options {
264
+ DeploymentValues : dv ,
265
+ Database : db ,
266
+ Pubsub : pubsub ,
267
+ IncludeProvisionerDaemon : true ,
268
+ },
269
+ })
270
+
271
+ _ = coderdtest .CreateFirstUser (t , client )
272
+ _ = coderdenttest .AddLicense (t , client , coderdenttest.LicenseOptions {
273
+ Features : license.Features {
274
+ codersdk .FeatureWorkspaceProxy : 1 ,
275
+ },
276
+ })
277
+
278
+ return client , db
279
+ }
280
+
281
+ t .Run ("OK" , func (t * testing.T ) {
282
+ t .Parallel ()
283
+
284
+ client , db := setup (t )
285
+
286
+ ctx := testutil .Context (t , testutil .WaitLong )
287
+ const (
288
+ proxyName = "hello"
289
+ proxyDisplayName = "Hello World"
290
+ proxyIcon = "/emojis/flag.png"
291
+ )
292
+ createRes , err := client .CreateWorkspaceProxy (ctx , codersdk.CreateWorkspaceProxyRequest {
293
+ Name : proxyName ,
294
+ DisplayName : proxyDisplayName ,
295
+ Icon : proxyIcon ,
296
+ })
297
+ require .NoError (t , err )
298
+
299
+ proxyClient := wsproxysdk .New (client .URL )
300
+ proxyClient .SetSessionToken (createRes .ProxyToken )
301
+
302
+ // Register
303
+ req := wsproxysdk.RegisterWorkspaceProxyRequest {
304
+ AccessURL : "https://proxy.coder.test" ,
305
+ WildcardHostname : "*.proxy.coder.test" ,
306
+ DerpEnabled : true ,
307
+ ReplicaID : uuid .New (),
308
+ ReplicaHostname : "mars" ,
309
+ ReplicaError : "" ,
310
+ ReplicaRelayAddress : "http://127.0.0.1:8080" ,
311
+ Version : buildinfo .Version (),
312
+ }
313
+ registerRes1 , err := proxyClient .RegisterWorkspaceProxy (ctx , req )
314
+ require .NoError (t , err )
315
+ require .NotEmpty (t , registerRes1 .AppSecurityKey )
316
+ require .NotEmpty (t , registerRes1 .DERPMeshKey )
317
+ require .EqualValues (t , 10001 , registerRes1 .DERPRegionID )
318
+ require .Empty (t , registerRes1 .SiblingReplicas )
319
+
320
+ // Get the proxy to ensure fields have updated.
321
+ // TODO: we don't have a way to get the proxy by ID yet.
322
+ proxies , err := client .WorkspaceProxies (ctx )
323
+ require .NoError (t , err )
324
+ require .Len (t , proxies , 1 )
325
+ require .Equal (t , createRes .Proxy .ID , proxies [0 ].ID )
326
+ require .Equal (t , proxyName , proxies [0 ].Name )
327
+ require .Equal (t , proxyDisplayName , proxies [0 ].DisplayName )
328
+ require .Equal (t , proxyIcon , proxies [0 ].Icon )
329
+ require .Equal (t , req .AccessURL , proxies [0 ].URL )
330
+ require .Equal (t , req .AccessURL , proxies [0 ].URL )
331
+ require .Equal (t , req .WildcardHostname , proxies [0 ].WildcardHostname )
332
+ require .Equal (t , req .DerpEnabled , proxies [0 ].DerpEnabled )
333
+ require .False (t , proxies [0 ].Deleted )
334
+
335
+ // Get the replica from the DB.
336
+ replica , err := db .GetReplicaByID (ctx , req .ReplicaID )
337
+ require .NoError (t , err )
338
+ require .Equal (t , req .ReplicaID , replica .ID )
339
+ require .Equal (t , req .ReplicaHostname , replica .Hostname )
340
+ require .Equal (t , req .ReplicaError , replica .Error )
341
+ require .Equal (t , req .ReplicaRelayAddress , replica .RelayAddress )
342
+ require .Equal (t , req .Version , replica .Version )
343
+ require .EqualValues (t , 10001 , replica .RegionID )
344
+ require .False (t , replica .StoppedAt .Valid )
345
+ require .Zero (t , replica .DatabaseLatency )
346
+ require .False (t , replica .Primary )
347
+
348
+ // Re-register with most fields changed.
349
+ req = wsproxysdk.RegisterWorkspaceProxyRequest {
350
+ AccessURL : "https://cool.proxy.coder.test" ,
351
+ WildcardHostname : "*.cool.proxy.coder.test" ,
352
+ DerpEnabled : false ,
353
+ ReplicaID : req .ReplicaID ,
354
+ ReplicaHostname : "venus" ,
355
+ ReplicaError : "error" ,
356
+ ReplicaRelayAddress : "http://127.0.0.1:9090" ,
357
+ Version : buildinfo .Version (),
358
+ }
359
+ registerRes2 , err := proxyClient .RegisterWorkspaceProxy (ctx , req )
360
+ require .NoError (t , err )
361
+ require .Equal (t , registerRes1 , registerRes2 )
362
+
363
+ // Get the proxy to ensure nothing has changed except updated_at.
364
+ // TODO: we don't have a way to get the proxy by ID yet.
365
+ proxiesNew , err := client .WorkspaceProxies (ctx )
366
+ require .NoError (t , err )
367
+ require .Len (t , proxiesNew , 1 )
368
+ require .Equal (t , createRes .Proxy .ID , proxiesNew [0 ].ID )
369
+ require .Equal (t , proxyName , proxiesNew [0 ].Name )
370
+ require .Equal (t , proxyDisplayName , proxiesNew [0 ].DisplayName )
371
+ require .Equal (t , proxyIcon , proxiesNew [0 ].Icon )
372
+ require .Equal (t , req .AccessURL , proxiesNew [0 ].URL )
373
+ require .Equal (t , req .AccessURL , proxiesNew [0 ].URL )
374
+ require .Equal (t , req .WildcardHostname , proxiesNew [0 ].WildcardHostname )
375
+ require .Equal (t , req .DerpEnabled , proxiesNew [0 ].DerpEnabled )
376
+ require .False (t , proxiesNew [0 ].Deleted )
377
+
378
+ // Get the replica from the DB and ensure the fields have been updated,
379
+ // especially the updated_at.
380
+ replica , err = db .GetReplicaByID (ctx , req .ReplicaID )
381
+ require .NoError (t , err )
382
+ require .Equal (t , req .ReplicaID , replica .ID )
383
+ require .Equal (t , req .ReplicaHostname , replica .Hostname )
384
+ require .Equal (t , req .ReplicaError , replica .Error )
385
+ require .Equal (t , req .ReplicaRelayAddress , replica .RelayAddress )
386
+ require .Equal (t , req .Version , replica .Version )
387
+ require .EqualValues (t , 10001 , replica .RegionID )
388
+ require .False (t , replica .StoppedAt .Valid )
389
+ require .Zero (t , replica .DatabaseLatency )
390
+ require .False (t , replica .Primary )
391
+
392
+ // Deregister
393
+ err = proxyClient .DeregisterWorkspaceProxy (ctx , wsproxysdk.DeregisterWorkspaceProxyRequest {
394
+ ReplicaID : req .ReplicaID ,
395
+ })
396
+ require .NoError (t , err )
397
+
398
+ // Ensure the replica has been fully stopped.
399
+ replica , err = db .GetReplicaByID (ctx , req .ReplicaID )
400
+ require .NoError (t , err )
401
+ require .Equal (t , req .ReplicaID , replica .ID )
402
+ require .True (t , replica .StoppedAt .Valid )
403
+
404
+ // Re-register should fail
405
+ _ , err = proxyClient .RegisterWorkspaceProxy (ctx , wsproxysdk.RegisterWorkspaceProxyRequest {})
406
+ require .Error (t , err )
407
+ })
408
+
409
+ t .Run ("BlockMismatchingVersion" , func (t * testing.T ) {
410
+ t .Parallel ()
411
+
412
+ client , _ := setup (t )
413
+
414
+ ctx := testutil .Context (t , testutil .WaitLong )
415
+ createRes , err := client .CreateWorkspaceProxy (ctx , codersdk.CreateWorkspaceProxyRequest {
416
+ Name : "hi" ,
417
+ })
418
+ require .NoError (t , err )
419
+
420
+ proxyClient := wsproxysdk .New (client .URL )
421
+ proxyClient .SetSessionToken (createRes .ProxyToken )
422
+
423
+ _ , err = proxyClient .RegisterWorkspaceProxy (ctx , wsproxysdk.RegisterWorkspaceProxyRequest {
424
+ AccessURL : "https://proxy.coder.test" ,
425
+ WildcardHostname : "*.proxy.coder.test" ,
426
+ DerpEnabled : true ,
427
+ ReplicaID : uuid .New (),
428
+ ReplicaHostname : "mars" ,
429
+ ReplicaError : "" ,
430
+ ReplicaRelayAddress : "http://127.0.0.1:8080" ,
431
+ Version : "v0.0.0" ,
432
+ })
433
+ require .Error (t , err )
434
+ var sdkErr * codersdk.Error
435
+ require .ErrorAs (t , err , & sdkErr )
436
+ require .Equal (t , http .StatusBadRequest , sdkErr .StatusCode ())
437
+ require .Contains (t , sdkErr .Response .Message , "Version mismatch" )
438
+ })
439
+
440
+ t .Run ("ReregisterUpdateReplica" , func (t * testing.T ) {
441
+ t .Parallel ()
442
+
443
+ client , db := setup (t )
444
+
445
+ ctx := testutil .Context (t , testutil .WaitLong )
446
+ createRes , err := client .CreateWorkspaceProxy (ctx , codersdk.CreateWorkspaceProxyRequest {
447
+ Name : "hi" ,
448
+ })
449
+ require .NoError (t , err )
450
+
451
+ proxyClient := wsproxysdk .New (client .URL )
452
+ proxyClient .SetSessionToken (createRes .ProxyToken )
453
+
454
+ req := wsproxysdk.RegisterWorkspaceProxyRequest {
455
+ AccessURL : "https://proxy.coder.test" ,
456
+ WildcardHostname : "*.proxy.coder.test" ,
457
+ DerpEnabled : true ,
458
+ ReplicaID : uuid .New (),
459
+ ReplicaHostname : "mars" ,
460
+ ReplicaError : "" ,
461
+ ReplicaRelayAddress : "http://127.0.0.1:8080" ,
462
+ Version : buildinfo .Version (),
463
+ }
464
+ _ , err = proxyClient .RegisterWorkspaceProxy (ctx , req )
465
+ require .NoError (t , err )
466
+
467
+ // Get the replica from the DB.
468
+ replica , err := db .GetReplicaByID (ctx , req .ReplicaID )
469
+ require .NoError (t , err )
470
+ require .Equal (t , req .ReplicaID , replica .ID )
471
+
472
+ time .Sleep (time .Millisecond )
473
+
474
+ // Re-register with no changed fields.
475
+ _ , err = proxyClient .RegisterWorkspaceProxy (ctx , req )
476
+ require .NoError (t , err )
477
+
478
+ // Get the replica from the DB and make sure updated_at has changed.
479
+ replica , err = db .GetReplicaByID (ctx , req .ReplicaID )
480
+ require .NoError (t , err )
481
+ require .Equal (t , req .ReplicaID , replica .ID )
482
+ require .Greater (t , replica .UpdatedAt .UnixNano (), replica .CreatedAt .UnixNano ())
483
+ })
484
+
485
+ t .Run ("DeregisterNonExistentReplica" , func (t * testing.T ) {
486
+ t .Parallel ()
487
+
488
+ client , _ := setup (t )
489
+
490
+ ctx := testutil .Context (t , testutil .WaitLong )
491
+ createRes , err := client .CreateWorkspaceProxy (ctx , codersdk.CreateWorkspaceProxyRequest {
492
+ Name : "hi" ,
493
+ })
494
+ require .NoError (t , err )
495
+
496
+ proxyClient := wsproxysdk .New (client .URL )
497
+ proxyClient .SetSessionToken (createRes .ProxyToken )
498
+
499
+ err = proxyClient .DeregisterWorkspaceProxy (ctx , wsproxysdk.DeregisterWorkspaceProxyRequest {
500
+ ReplicaID : uuid .New (),
501
+ })
502
+ require .Error (t , err )
503
+ var sdkErr * codersdk.Error
504
+ require .ErrorAs (t , err , & sdkErr )
505
+ require .Equal (t , http .StatusNotFound , sdkErr .StatusCode ())
506
+ })
507
+
508
+ t .Run ("ReturnSiblings" , func (t * testing.T ) {
509
+ t .Parallel ()
510
+
511
+ client , _ := setup (t )
512
+
513
+ ctx := testutil .Context (t , testutil .WaitLong )
514
+ createRes1 , err := client .CreateWorkspaceProxy (ctx , codersdk.CreateWorkspaceProxyRequest {
515
+ Name : "one" ,
516
+ })
517
+ require .NoError (t , err )
518
+ createRes2 , err := client .CreateWorkspaceProxy (ctx , codersdk.CreateWorkspaceProxyRequest {
519
+ Name : "two" ,
520
+ })
521
+ require .NoError (t , err )
522
+
523
+ // Register a replica on proxy 2. This shouldn't be returned by replicas
524
+ // for proxy 1.
525
+ proxyClient2 := wsproxysdk .New (client .URL )
526
+ proxyClient2 .SetSessionToken (createRes2 .ProxyToken )
527
+ _ , err = proxyClient2 .RegisterWorkspaceProxy (ctx , wsproxysdk.RegisterWorkspaceProxyRequest {
528
+ AccessURL : "https://other.proxy.coder.test" ,
529
+ WildcardHostname : "*.other.proxy.coder.test" ,
530
+ DerpEnabled : true ,
531
+ ReplicaID : uuid .New (),
532
+ ReplicaHostname : "venus" ,
533
+ ReplicaError : "" ,
534
+ ReplicaRelayAddress : "http://127.0.0.1:9090" ,
535
+ Version : buildinfo .Version (),
536
+ })
537
+ require .NoError (t , err )
538
+
539
+ // Register replica 1.
540
+ proxyClient1 := wsproxysdk .New (client .URL )
541
+ proxyClient1 .SetSessionToken (createRes1 .ProxyToken )
542
+ req1 := wsproxysdk.RegisterWorkspaceProxyRequest {
543
+ AccessURL : "https://one.proxy.coder.test" ,
544
+ WildcardHostname : "*.one.proxy.coder.test" ,
545
+ DerpEnabled : true ,
546
+ ReplicaID : uuid .New (),
547
+ ReplicaHostname : "mars1" ,
548
+ ReplicaError : "" ,
549
+ ReplicaRelayAddress : "http://127.0.0.1:8081" ,
550
+ Version : buildinfo .Version (),
551
+ }
552
+ registerRes1 , err := proxyClient1 .RegisterWorkspaceProxy (ctx , req1 )
553
+ require .NoError (t , err )
554
+ require .Empty (t , registerRes1 .SiblingReplicas )
555
+
556
+ // Register replica 2 and expect to get replica 1 as a sibling.
557
+ req2 := wsproxysdk.RegisterWorkspaceProxyRequest {
558
+ AccessURL : "https://two.proxy.coder.test" ,
559
+ WildcardHostname : "*.two.proxy.coder.test" ,
560
+ DerpEnabled : true ,
561
+ ReplicaID : uuid .New (),
562
+ ReplicaHostname : "mars2" ,
563
+ ReplicaError : "" ,
564
+ ReplicaRelayAddress : "http://127.0.0.1:8082" ,
565
+ Version : buildinfo .Version (),
566
+ }
567
+ registerRes2 , err := proxyClient1 .RegisterWorkspaceProxy (ctx , req2 )
568
+ require .NoError (t , err )
569
+ require .Len (t , registerRes2 .SiblingReplicas , 1 )
570
+ require .Equal (t , req1 .ReplicaID , registerRes2 .SiblingReplicas [0 ].ID )
571
+ require .Equal (t , req1 .ReplicaHostname , registerRes2 .SiblingReplicas [0 ].Hostname )
572
+ require .Equal (t , req1 .ReplicaRelayAddress , registerRes2 .SiblingReplicas [0 ].RelayAddress )
573
+ require .EqualValues (t , 10001 , registerRes2 .SiblingReplicas [0 ].RegionID )
574
+
575
+ // Re-register replica 1 and expect to get replica 2 as a sibling.
576
+ registerRes1 , err = proxyClient1 .RegisterWorkspaceProxy (ctx , req1 )
577
+ require .NoError (t , err )
578
+ require .Len (t , registerRes1 .SiblingReplicas , 1 )
579
+ require .Equal (t , req2 .ReplicaID , registerRes1 .SiblingReplicas [0 ].ID )
580
+ require .Equal (t , req2 .ReplicaHostname , registerRes1 .SiblingReplicas [0 ].Hostname )
581
+ require .Equal (t , req2 .ReplicaRelayAddress , registerRes1 .SiblingReplicas [0 ].RelayAddress )
582
+ require .EqualValues (t , 10001 , registerRes1 .SiblingReplicas [0 ].RegionID )
583
+ })
584
+ }
585
+
248
586
func TestIssueSignedAppToken (t * testing.T ) {
249
587
t .Parallel ()
250
588
0 commit comments