Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/supervisor/pkg/ports/ports_test.go
2500 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package ports
6
7
import (
8
"context"
9
"io"
10
"net"
11
"sync"
12
"testing"
13
"time"
14
15
"github.com/gitpod-io/gitpod/common-go/log"
16
gitpod "github.com/gitpod-io/gitpod/gitpod-protocol"
17
"github.com/gitpod-io/gitpod/supervisor/api"
18
"github.com/google/go-cmp/cmp"
19
"github.com/google/go-cmp/cmp/cmpopts"
20
"github.com/sirupsen/logrus"
21
"golang.org/x/sync/errgroup"
22
)
23
24
func TestPortsUpdateState(t *testing.T) {
25
type ExposureExpectation []ExposedPort
26
type UpdateExpectation [][]*api.PortsStatus
27
type ConfigChange struct {
28
instance []*gitpod.PortsItems
29
}
30
type Change struct {
31
Config *ConfigChange
32
Served []ServedPort
33
Exposed []ExposedPort
34
Tunneled []PortTunnelState
35
ConfigErr error
36
ServedErr error
37
ExposedErr error
38
TunneledErr error
39
}
40
tests := []struct {
41
Desc string
42
InternalPorts []uint32
43
Changes []Change
44
ExpectedExposure ExposureExpectation
45
ExpectedUpdates UpdateExpectation
46
}{
47
{
48
Desc: "basic locally served",
49
Changes: []Change{
50
{Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 8080, true}}},
51
{Exposed: []ExposedPort{{LocalPort: 8080, URL: "foobar"}}},
52
{Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 8080, true}, {net.IPv4zero, 60000, false}}},
53
{Served: []ServedPort{{net.IPv4zero, 60000, false}}},
54
{Served: []ServedPort{}},
55
},
56
ExpectedExposure: []ExposedPort{
57
{LocalPort: 8080},
58
{LocalPort: 60000},
59
},
60
ExpectedUpdates: UpdateExpectation{
61
{},
62
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private}},
63
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{OnExposed: api.OnPortExposedAction_notify_private, Visibility: api.PortVisibility_private, Url: "foobar"}}},
64
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{OnExposed: api.OnPortExposedAction_notify_private, Visibility: api.PortVisibility_private, Url: "foobar"}}, {LocalPort: 60000, Served: true}},
65
[]*api.PortsStatus{{LocalPort: 8080, Served: false, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{OnExposed: api.OnPortExposedAction_notify_private, Visibility: api.PortVisibility_private, Url: "foobar"}}, {LocalPort: 60000, Served: true}},
66
[]*api.PortsStatus{{LocalPort: 8080, Served: false, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{OnExposed: api.OnPortExposedAction_notify_private, Visibility: api.PortVisibility_private, Url: "foobar"}}},
67
},
68
},
69
{
70
Desc: "basic globally served",
71
Changes: []Change{
72
{Served: []ServedPort{{net.IPv4zero, 8080, false}}},
73
{Served: []ServedPort{}},
74
},
75
ExpectedExposure: []ExposedPort{
76
{LocalPort: 8080},
77
},
78
ExpectedUpdates: UpdateExpectation{
79
{},
80
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private}},
81
{},
82
},
83
},
84
{
85
Desc: "basic port publically exposed",
86
Changes: []Change{
87
{Served: []ServedPort{{Port: 8080}}},
88
{Exposed: []ExposedPort{{LocalPort: 8080, Public: true, URL: "foobar"}}},
89
{Exposed: []ExposedPort{{LocalPort: 8080, Public: false, URL: "foobar"}}},
90
},
91
ExpectedExposure: ExposureExpectation{
92
{LocalPort: 8080},
93
},
94
ExpectedUpdates: UpdateExpectation{
95
{},
96
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private}},
97
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, Url: "foobar", OnExposed: api.OnPortExposedAction_notify_private}}},
98
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, Url: "foobar", OnExposed: api.OnPortExposedAction_notify_private}}},
99
},
100
},
101
{
102
Desc: "internal ports served",
103
InternalPorts: []uint32{8080},
104
Changes: []Change{
105
{Served: []ServedPort{}},
106
{Served: []ServedPort{{net.IPv4zero, 8080, false}}},
107
},
108
ExpectedExposure: ExposureExpectation(nil),
109
ExpectedUpdates: UpdateExpectation{{}},
110
},
111
{
112
Desc: "serving port from the configured port range",
113
Changes: []Change{
114
{Config: &ConfigChange{
115
instance: []*gitpod.PortsItems{{
116
OnOpen: "open-browser",
117
Port: "4000-5000",
118
}},
119
}},
120
{Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 4040, true}}},
121
{Exposed: []ExposedPort{{LocalPort: 4040, Public: true, URL: "4040-foobar"}}},
122
{Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 4040, true}, {net.IPv4zero, 60000, false}}},
123
},
124
ExpectedExposure: []ExposedPort{
125
{LocalPort: 4040},
126
{LocalPort: 60000},
127
},
128
ExpectedUpdates: UpdateExpectation{
129
{},
130
{},
131
[]*api.PortsStatus{{LocalPort: 4040, Served: true, OnOpen: api.PortsStatus_open_browser}},
132
[]*api.PortsStatus{{LocalPort: 4040, Served: true, OnOpen: api.PortsStatus_open_browser, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, Url: "4040-foobar", OnExposed: api.OnPortExposedAction_open_browser}}},
133
[]*api.PortsStatus{
134
{LocalPort: 4040, Served: true, OnOpen: api.PortsStatus_open_browser, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, Url: "4040-foobar", OnExposed: api.OnPortExposedAction_open_browser}},
135
{LocalPort: 60000, Served: true},
136
},
137
},
138
},
139
{
140
Desc: "auto expose configured ports",
141
Changes: []Change{
142
{
143
Config: &ConfigChange{instance: []*gitpod.PortsItems{
144
{Port: 8080, Visibility: "private"},
145
}},
146
},
147
{
148
Exposed: []ExposedPort{{LocalPort: 8080, Public: false, URL: "foobar"}},
149
},
150
{
151
Exposed: []ExposedPort{{LocalPort: 8080, Public: true, URL: "foobar"}},
152
},
153
{
154
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 8080, true}},
155
},
156
{
157
Exposed: []ExposedPort{{LocalPort: 8080, Public: true, URL: "foobar"}},
158
},
159
{
160
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 8080, true}},
161
},
162
{
163
Served: []ServedPort{},
164
},
165
{
166
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 8080, false}},
167
},
168
},
169
ExpectedExposure: []ExposedPort{
170
{LocalPort: 8080, Public: false},
171
},
172
ExpectedUpdates: UpdateExpectation{
173
{},
174
[]*api.PortsStatus{{LocalPort: 8080, OnOpen: api.PortsStatus_notify}},
175
[]*api.PortsStatus{{LocalPort: 8080, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
176
[]*api.PortsStatus{{LocalPort: 8080, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
177
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
178
[]*api.PortsStatus{{LocalPort: 8080, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
179
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
180
},
181
},
182
{
183
Desc: "starting multiple proxies for the same served event",
184
Changes: []Change{
185
{
186
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 8080, true}, {net.IPv4zero, 3000, true}},
187
},
188
},
189
ExpectedExposure: []ExposedPort{
190
{LocalPort: 8080},
191
{LocalPort: 3000},
192
},
193
ExpectedUpdates: UpdateExpectation{
194
{},
195
{
196
{LocalPort: 3000, Served: true, OnOpen: api.PortsStatus_notify_private},
197
{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private},
198
},
199
},
200
},
201
{
202
Desc: "served between auto exposing configured and exposed update",
203
Changes: []Change{
204
{
205
Config: &ConfigChange{instance: []*gitpod.PortsItems{
206
{Port: 8080, Visibility: "private"},
207
}},
208
},
209
{
210
Served: []ServedPort{{net.IPv4zero, 8080, false}},
211
},
212
{
213
Exposed: []ExposedPort{{LocalPort: 8080, Public: false, URL: "foobar"}},
214
},
215
},
216
ExpectedExposure: []ExposedPort{
217
{LocalPort: 8080},
218
},
219
ExpectedUpdates: UpdateExpectation{
220
{},
221
{{LocalPort: 8080, OnOpen: api.PortsStatus_notify}},
222
{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify}},
223
{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
224
},
225
},
226
{
227
Desc: "the same port served locally and then globally too, prefer globally (exposed in between)",
228
Changes: []Change{
229
{
230
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}},
231
},
232
{
233
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
234
},
235
{
236
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}, {net.IPv4zero, 5900, false}},
237
},
238
{
239
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
240
},
241
},
242
ExpectedExposure: []ExposedPort{
243
{LocalPort: 5900},
244
},
245
ExpectedUpdates: UpdateExpectation{
246
{},
247
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
248
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
249
},
250
},
251
{
252
Desc: "the same port served locally and then globally too, prefer globally (exposed after)",
253
Changes: []Change{
254
{
255
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}},
256
},
257
{
258
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}, {net.IPv4zero, 5900, false}},
259
},
260
{
261
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
262
},
263
{
264
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
265
},
266
},
267
ExpectedExposure: []ExposedPort{
268
{LocalPort: 5900},
269
},
270
ExpectedUpdates: UpdateExpectation{
271
{},
272
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
273
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
274
},
275
},
276
{
277
Desc: "the same port served globally and then locally too, prefer globally (exposed in between)",
278
Changes: []Change{
279
{
280
Served: []ServedPort{{net.IPv4zero, 5900, false}},
281
},
282
{
283
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
284
},
285
{
286
Served: []ServedPort{{net.IPv4zero, 5900, false}, {net.IPv4(127, 0, 0, 1), 5900, true}},
287
},
288
},
289
ExpectedExposure: []ExposedPort{
290
{LocalPort: 5900},
291
},
292
ExpectedUpdates: UpdateExpectation{
293
{},
294
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
295
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
296
},
297
},
298
{
299
Desc: "the same port served globally and then locally too, prefer globally (exposed after)",
300
Changes: []Change{
301
{
302
Served: []ServedPort{{net.IPv4zero, 5900, false}},
303
},
304
{
305
Served: []ServedPort{{net.IPv4zero, 5900, false}, {net.IPv4(127, 0, 0, 1), 5900, true}},
306
},
307
{
308
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
309
},
310
},
311
ExpectedExposure: []ExposedPort{
312
{LocalPort: 5900},
313
},
314
ExpectedUpdates: UpdateExpectation{
315
{},
316
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
317
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
318
},
319
},
320
{
321
Desc: "the same port served locally on ip4 and then locally on ip6 too, prefer first (exposed in between)",
322
Changes: []Change{
323
{
324
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}},
325
},
326
{
327
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
328
},
329
{
330
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}, {net.IPv6zero, 5900, true}},
331
},
332
},
333
ExpectedExposure: []ExposedPort{
334
{LocalPort: 5900},
335
},
336
ExpectedUpdates: UpdateExpectation{
337
{},
338
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
339
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
340
},
341
},
342
{
343
Desc: "the same port served locally on ip4 and then locally on ip6 too, prefer first (exposed after)",
344
Changes: []Change{
345
{
346
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}},
347
},
348
{
349
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}, {net.IPv6zero, 5900, true}},
350
},
351
{
352
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
353
},
354
},
355
ExpectedExposure: []ExposedPort{
356
{LocalPort: 5900},
357
},
358
ExpectedUpdates: UpdateExpectation{
359
{},
360
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
361
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
362
},
363
},
364
{
365
Desc: "the same port served locally on ip4 and then globally on ip6 too, prefer first (exposed in between)",
366
Changes: []Change{
367
{
368
Served: []ServedPort{{net.IPv4zero, 5900, false}},
369
},
370
{
371
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
372
},
373
{
374
Served: []ServedPort{{net.IPv4zero, 5900, false}, {net.IPv6zero, 5900, false}},
375
},
376
},
377
ExpectedExposure: []ExposedPort{
378
{LocalPort: 5900},
379
},
380
ExpectedUpdates: UpdateExpectation{
381
{},
382
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
383
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
384
},
385
},
386
{
387
Desc: "the same port served locally on ip4 and then globally on ip6 too, prefer first (exposed after)",
388
Changes: []Change{
389
{
390
Served: []ServedPort{{net.IPv4zero, 5900, false}},
391
},
392
{
393
Served: []ServedPort{{net.IPv4zero, 5900, false}, {net.IPv6zero, 5900, false}},
394
},
395
{
396
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
397
},
398
},
399
ExpectedExposure: []ExposedPort{
400
{LocalPort: 5900},
401
},
402
ExpectedUpdates: UpdateExpectation{
403
{},
404
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
405
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
406
},
407
},
408
{
409
Desc: "port status has description set as soon as the port gets exposed, if there was a description configured",
410
Changes: []Change{
411
{
412
Config: &ConfigChange{instance: []*gitpod.PortsItems{
413
{Port: 8080, Visibility: "private", Description: "Development server"},
414
}},
415
},
416
{
417
Served: []ServedPort{{net.IPv4zero, 8080, false}},
418
},
419
{
420
Exposed: []ExposedPort{{LocalPort: 8080, Public: false, URL: "foobar"}},
421
},
422
},
423
ExpectedExposure: []ExposedPort{
424
{LocalPort: 8080},
425
},
426
ExpectedUpdates: UpdateExpectation{
427
{},
428
{{LocalPort: 8080, Description: "Development server", OnOpen: api.PortsStatus_notify}},
429
{{LocalPort: 8080, Description: "Development server", Served: true, OnOpen: api.PortsStatus_notify}},
430
{{LocalPort: 8080, Description: "Development server", Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
431
},
432
},
433
{
434
Desc: "port status has the name attribute set as soon as the port gets exposed, if there was a name configured in Gitpod's Workspace",
435
Changes: []Change{
436
{
437
Config: &ConfigChange{instance: []*gitpod.PortsItems{
438
{Port: 3000, Visibility: "private", Name: "react"},
439
}},
440
},
441
{
442
Served: []ServedPort{{net.IPv4zero, 3000, false}},
443
},
444
{
445
Exposed: []ExposedPort{{LocalPort: 3000, Public: false, URL: "foobar"}},
446
},
447
},
448
ExpectedExposure: []ExposedPort{
449
{LocalPort: 3000},
450
},
451
ExpectedUpdates: UpdateExpectation{
452
{},
453
{{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify}},
454
{{LocalPort: 3000, Name: "react", Served: true, OnOpen: api.PortsStatus_notify}},
455
{{LocalPort: 3000, Name: "react", Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
456
},
457
},
458
{
459
Desc: "change configed ports order",
460
Changes: []Change{
461
{
462
Config: &ConfigChange{instance: []*gitpod.PortsItems{
463
{Port: 3001, Visibility: "private", Name: "react"},
464
{Port: 3000, Visibility: "private", Name: "react"},
465
}},
466
},
467
{
468
Config: &ConfigChange{instance: []*gitpod.PortsItems{
469
{Port: "5000-5999", Visibility: "private", Name: "react"},
470
{Port: 3001, Visibility: "private", Name: "react"},
471
{Port: 3000, Visibility: "private", Name: "react"},
472
}},
473
},
474
{
475
Served: []ServedPort{{net.IPv4zero, 5002, false}},
476
},
477
{
478
Served: []ServedPort{{net.IPv4zero, 5002, false}, {net.IPv4zero, 5001, false}},
479
},
480
{
481
Config: &ConfigChange{instance: []*gitpod.PortsItems{
482
{Port: 3000, Visibility: "private", Name: "react"},
483
{Port: 3001, Visibility: "private", Name: "react"},
484
}},
485
},
486
{
487
Served: []ServedPort{{net.IPv4zero, 5001, false}, {net.IPv4zero, 3000, false}},
488
},
489
{
490
Exposed: []ExposedPort{{LocalPort: 3000, Public: false, URL: "foobar"}},
491
},
492
},
493
ExpectedExposure: []ExposedPort{
494
{LocalPort: 5002},
495
{LocalPort: 5001},
496
{LocalPort: 3000},
497
{LocalPort: 3001},
498
},
499
ExpectedUpdates: UpdateExpectation{
500
{},
501
{
502
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
503
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
504
},
505
{
506
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
507
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
508
},
509
{
510
{LocalPort: 5002, Name: "react", Served: true, OnOpen: api.PortsStatus_notify},
511
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
512
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
513
},
514
{
515
{LocalPort: 5001, Name: "react", Served: true, OnOpen: api.PortsStatus_notify},
516
{LocalPort: 5002, Name: "react", Served: true, OnOpen: api.PortsStatus_notify},
517
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
518
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
519
},
520
{
521
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
522
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
523
{LocalPort: 5001, Served: true, OnOpen: api.PortsStatus_notify_private},
524
{LocalPort: 5002, Served: true, OnOpen: api.PortsStatus_notify_private},
525
},
526
{
527
{LocalPort: 3000, Name: "react", Served: true, OnOpen: api.PortsStatus_notify},
528
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
529
{LocalPort: 5001, Served: true, OnOpen: api.PortsStatus_notify_private},
530
},
531
{
532
{LocalPort: 3000, Name: "react", Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}},
533
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
534
{LocalPort: 5001, Served: true, OnOpen: api.PortsStatus_notify_private},
535
},
536
},
537
},
538
{
539
Desc: "change configed ports order with ranged covered not ranged",
540
Changes: []Change{
541
{
542
Config: &ConfigChange{
543
instance: []*gitpod.PortsItems{
544
{Port: 3001, Visibility: "private", Name: "react"},
545
{Port: 3000, Visibility: "private", Name: "react"},
546
},
547
},
548
},
549
{
550
Config: &ConfigChange{
551
instance: []*gitpod.PortsItems{
552
{Port: 3003, Visibility: "private", Name: "react"},
553
{Port: 3001, Visibility: "private", Name: "react"},
554
{Port: "3001-3005", Visibility: "private", Name: "react"},
555
{Port: 3000, Visibility: "private", Name: "react"},
556
},
557
},
558
},
559
{
560
Served: []ServedPort{{net.IPv4zero, 3000, false}},
561
},
562
{
563
Served: []ServedPort{{net.IPv4zero, 3000, false}, {net.IPv4zero, 3001, false}, {net.IPv4zero, 3002, false}},
564
},
565
{
566
Config: &ConfigChange{
567
instance: []*gitpod.PortsItems{
568
{Port: 3003, Visibility: "private", Name: "react"},
569
{Port: 3000, Visibility: "private", Name: "react"},
570
},
571
},
572
},
573
{
574
Config: &ConfigChange{
575
instance: []*gitpod.PortsItems{
576
{Port: "3001-3005", Visibility: "private", Name: "react"},
577
{Port: 3003, Visibility: "private", Name: "react"},
578
{Port: 3000, Visibility: "private", Name: "react"},
579
},
580
},
581
},
582
},
583
ExpectedExposure: []ExposedPort{
584
{LocalPort: 3000},
585
{LocalPort: 3001},
586
{LocalPort: 3002},
587
{LocalPort: 3003},
588
},
589
ExpectedUpdates: UpdateExpectation{
590
{},
591
{
592
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
593
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
594
},
595
{
596
{LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify},
597
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
598
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
599
},
600
{
601
{LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify},
602
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
603
{LocalPort: 3000, Served: true, Name: "react", OnOpen: api.PortsStatus_notify},
604
},
605
{
606
{LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify},
607
{LocalPort: 3001, Served: true, Name: "react", OnOpen: api.PortsStatus_notify},
608
{LocalPort: 3002, Served: true, Name: "react", OnOpen: api.PortsStatus_notify},
609
{LocalPort: 3000, Served: true, Name: "react", OnOpen: api.PortsStatus_notify},
610
},
611
{
612
{LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify},
613
{LocalPort: 3000, Served: true, Name: "react", OnOpen: api.PortsStatus_notify},
614
{LocalPort: 3001, Served: true, OnOpen: api.PortsStatus_notify_private},
615
{LocalPort: 3002, Served: true, OnOpen: api.PortsStatus_notify_private},
616
},
617
{
618
{LocalPort: 3001, Name: "react", Served: true, OnOpen: api.PortsStatus_notify},
619
{LocalPort: 3002, Name: "react", Served: true, OnOpen: api.PortsStatus_notify},
620
{LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify},
621
{LocalPort: 3000, Served: true, Name: "react", OnOpen: api.PortsStatus_notify},
622
},
623
},
624
},
625
{
626
// Please make sure this test pass for code browser resolveExternalPort
627
// see also https://github.com/gitpod-io/openvscode-server/blob/5ab7644a8bbf37d28e23212bc6f1529cafd8bf7b/extensions/gitpod-web/src/extension.ts#L310-L339
628
Desc: "expose port without served, port should be responded for use case of openvscode-server",
629
Changes: []Change{
630
{
631
Exposed: []ExposedPort{{LocalPort: 3000, Public: false, URL: "foobar"}},
632
},
633
},
634
// this will not exposed because test manager didn't implement it properly
635
// ExpectedExposure: []ExposedPort{
636
// {LocalPort: 3000},
637
// },
638
ExpectedUpdates: UpdateExpectation{
639
{},
640
{
641
{LocalPort: 3000, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}},
642
},
643
},
644
},
645
}
646
647
log.Log.Logger.SetLevel(logrus.FatalLevel)
648
649
for _, test := range tests {
650
t.Run(test.Desc, func(t *testing.T) {
651
var (
652
exposed = &testExposedPorts{
653
Changes: make(chan []ExposedPort),
654
Error: make(chan error, 1),
655
}
656
served = &testServedPorts{
657
Changes: make(chan []ServedPort),
658
Error: make(chan error, 1),
659
}
660
config = &testConfigService{
661
Changes: make(chan *Configs),
662
Error: make(chan error, 1),
663
}
664
tunneled = &testTunneledPorts{
665
Changes: make(chan []PortTunnelState),
666
Error: make(chan error, 1),
667
}
668
669
pm = NewManager(exposed, served, config, tunneled, test.InternalPorts...)
670
updts [][]*api.PortsStatus
671
)
672
pm.proxyStarter = func(port uint32) (io.Closer, error) {
673
return io.NopCloser(nil), nil
674
}
675
676
ctx, cancel := context.WithCancel(context.Background())
677
defer cancel()
678
var wg sync.WaitGroup
679
wg.Add(3)
680
go pm.Run(ctx, &wg)
681
sub, err := pm.Subscribe()
682
if err != nil {
683
t.Fatal(err)
684
}
685
go func() {
686
defer wg.Done()
687
defer sub.Close(true)
688
689
for up := range sub.Updates() {
690
updts = append(updts, up)
691
}
692
}()
693
go func() {
694
defer wg.Done()
695
defer close(config.Error)
696
defer close(config.Changes)
697
defer close(served.Error)
698
defer close(served.Changes)
699
defer close(exposed.Error)
700
defer close(exposed.Changes)
701
defer close(tunneled.Error)
702
defer close(tunneled.Changes)
703
704
for _, c := range test.Changes {
705
if c.Config != nil {
706
change := &Configs{}
707
portConfigs, rangeConfigs := parseInstanceConfigs(c.Config.instance)
708
change.instancePortConfigs = portConfigs
709
change.instanceRangeConfigs = rangeConfigs
710
config.Changes <- change
711
} else if c.ConfigErr != nil {
712
config.Error <- c.ConfigErr
713
} else if c.Served != nil {
714
served.Changes <- c.Served
715
} else if c.ServedErr != nil {
716
served.Error <- c.ServedErr
717
} else if c.Exposed != nil {
718
exposed.Changes <- c.Exposed
719
} else if c.ExposedErr != nil {
720
exposed.Error <- c.ExposedErr
721
} else if c.Tunneled != nil {
722
tunneled.Changes <- c.Tunneled
723
} else if c.TunneledErr != nil {
724
tunneled.Error <- c.TunneledErr
725
}
726
}
727
}()
728
729
wg.Wait()
730
731
var (
732
sortExposed = cmpopts.SortSlices(func(x, y ExposedPort) bool { return x.LocalPort < y.LocalPort })
733
ignoreUnexported = cmpopts.IgnoreUnexported(
734
api.PortsStatus{},
735
api.ExposedPortInfo{},
736
)
737
)
738
if diff := cmp.Diff(test.ExpectedExposure, ExposureExpectation(exposed.Exposures), sortExposed, ignoreUnexported); diff != "" {
739
t.Errorf("unexpected exposures (-want +got):\n%s", diff)
740
}
741
742
if diff := cmp.Diff(test.ExpectedUpdates, UpdateExpectation(updts), ignoreUnexported); diff != "" {
743
t.Errorf("unexpected updates (-want +got):\n%s", diff)
744
}
745
})
746
}
747
}
748
749
type testTunneledPorts struct {
750
Changes chan []PortTunnelState
751
Error chan error
752
}
753
754
func (tep *testTunneledPorts) Observe(ctx context.Context) (<-chan []PortTunnelState, <-chan error) {
755
return tep.Changes, tep.Error
756
}
757
func (tep *testTunneledPorts) Tunnel(ctx context.Context, options *TunnelOptions, descs ...*PortTunnelDescription) ([]uint32, error) {
758
return nil, nil
759
}
760
func (tep *testTunneledPorts) CloseTunnel(ctx context.Context, localPorts ...uint32) ([]uint32, error) {
761
return nil, nil
762
}
763
func (tep *testTunneledPorts) EstablishTunnel(ctx context.Context, clientID string, localPort uint32, targetPort uint32) (net.Conn, error) {
764
return nil, nil
765
}
766
767
type testConfigService struct {
768
Changes chan *Configs
769
Error chan error
770
}
771
772
func (tep *testConfigService) Observe(ctx context.Context) (<-chan *Configs, <-chan error) {
773
return tep.Changes, tep.Error
774
}
775
776
type testExposedPorts struct {
777
Changes chan []ExposedPort
778
Error chan error
779
780
Exposures []ExposedPort
781
mu sync.Mutex
782
}
783
784
func (tep *testExposedPorts) Observe(ctx context.Context) (<-chan []ExposedPort, <-chan error) {
785
return tep.Changes, tep.Error
786
}
787
788
func (tep *testExposedPorts) Run(ctx context.Context) {
789
}
790
791
func (tep *testExposedPorts) Expose(ctx context.Context, local uint32, public bool, protocol string) <-chan error {
792
tep.mu.Lock()
793
defer tep.mu.Unlock()
794
795
tep.Exposures = append(tep.Exposures, ExposedPort{
796
LocalPort: local,
797
Public: public,
798
})
799
return nil
800
}
801
802
type testServedPorts struct {
803
Changes chan []ServedPort
804
Error chan error
805
}
806
807
func (tps *testServedPorts) Observe(ctx context.Context) (<-chan []ServedPort, <-chan error) {
808
return tps.Changes, tps.Error
809
}
810
811
// testing for deadlocks between subscribing and processing events
812
func TestPortsConcurrentSubscribe(t *testing.T) {
813
var (
814
subscribes = 100
815
subscribing = make(chan struct{})
816
exposed = &testExposedPorts{
817
Changes: make(chan []ExposedPort),
818
Error: make(chan error, 1),
819
}
820
served = &testServedPorts{
821
Changes: make(chan []ServedPort),
822
Error: make(chan error, 1),
823
}
824
config = &testConfigService{
825
Changes: make(chan *Configs),
826
Error: make(chan error, 1),
827
}
828
tunneled = &testTunneledPorts{
829
Changes: make(chan []PortTunnelState),
830
Error: make(chan error, 1),
831
}
832
pm = NewManager(exposed, served, config, tunneled)
833
)
834
pm.proxyStarter = func(local uint32) (io.Closer, error) {
835
return io.NopCloser(nil), nil
836
}
837
838
ctx, cancel := context.WithCancel(context.Background())
839
defer cancel()
840
var wg sync.WaitGroup
841
wg.Add(2)
842
go pm.Run(ctx, &wg)
843
go func() {
844
defer wg.Done()
845
defer close(config.Error)
846
defer close(config.Changes)
847
defer close(served.Error)
848
defer close(served.Changes)
849
defer close(exposed.Error)
850
defer close(exposed.Changes)
851
defer close(tunneled.Error)
852
defer close(tunneled.Changes)
853
854
var j uint32
855
for {
856
857
select {
858
case <-time.After(50 * time.Millisecond):
859
served.Changes <- []ServedPort{{Port: j}}
860
j++
861
case <-subscribing:
862
return
863
}
864
}
865
}()
866
867
for i := 0; i < maxSubscriptions; i++ {
868
eg, _ := errgroup.WithContext(context.Background())
869
eg.Go(func() error {
870
for j := 0; j < subscribes; j++ {
871
sub, err := pm.Subscribe()
872
if err != nil {
873
return err
874
}
875
// status
876
select {
877
case <-sub.Updates():
878
// update
879
case <-sub.Updates():
880
}
881
sub.Close(true)
882
}
883
return nil
884
})
885
err := eg.Wait()
886
if err != nil {
887
t.Fatal(err)
888
}
889
time.Sleep(50 * time.Millisecond)
890
}
891
close(subscribing)
892
893
wg.Wait()
894
}
895
896
func TestManager_getStatus(t *testing.T) {
897
type portState struct {
898
port uint32
899
notServed bool
900
}
901
type fields struct {
902
orderInYaml []any
903
state []portState
904
}
905
tests := []struct {
906
name string
907
fields fields
908
want []uint32
909
}{
910
{
911
name: "happy path",
912
fields: fields{
913
// The port number (e.g. 1337) or range (e.g. 3000-3999) to expose.
914
orderInYaml: []any{1002, 1000, "3000-3999", 1001},
915
state: []portState{{port: 1000}, {port: 1001}, {port: 1002}, {port: 3003}, {port: 3001}, {port: 3002}, {port: 4002}, {port: 4000}, {port: 5000}, {port: 5005}},
916
},
917
want: []uint32{1002, 1000, 3001, 3002, 3003, 1001, 4000, 4002, 5000, 5005},
918
},
919
{
920
name: "order for ranged ports and inside ranged order by number ASC",
921
fields: fields{
922
orderInYaml: []any{1002, "3000-3999", 1009, "4000-4999"},
923
state: []portState{{port: 5000}, {port: 1000}, {port: 1009}, {port: 4000}, {port: 4001}, {port: 3000}, {port: 3009}},
924
},
925
want: []uint32{3000, 3009, 1009, 4000, 4001, 1000, 5000},
926
},
927
{
928
name: "served ports order by number ASC",
929
fields: fields{
930
orderInYaml: []any{},
931
state: []portState{{port: 4000}, {port: 4003}, {port: 4007}, {port: 4001}, {port: 4006}},
932
},
933
want: []uint32{4000, 4001, 4003, 4006, 4007},
934
},
935
{
936
// Please make sure this test pass for code browser resolveExternalPort
937
// see also https://github.com/gitpod-io/openvscode-server/blob/5ab7644a8bbf37d28e23212bc6f1529cafd8bf7b/extensions/gitpod-web/src/extension.ts#L310-L339
938
name: "expose not served ports should respond their status",
939
fields: fields{
940
orderInYaml: []any{},
941
state: []portState{{port: 4000, notServed: true}},
942
},
943
want: []uint32{4000},
944
},
945
// It will not works because we do not `Run` ports Manger
946
// As ports Manger will autoExpose those ports (but not ranged port) in yaml
947
// and they will exists in state
948
// {
949
// name: "not ignore ports that not served but exists in yaml",
950
// fields: fields{
951
// orderInYaml: []any{1002, 1000, 1001},
952
// state: []uint32{},
953
// },
954
// want: []uint32{1002, 1000, 1001},
955
// },
956
}
957
for _, tt := range tests {
958
t.Run(tt.name, func(t *testing.T) {
959
state := make(map[uint32]*managedPort)
960
for _, s := range tt.fields.state {
961
state[s.port] = &managedPort{
962
Served: !s.notServed,
963
LocalhostPort: s.port,
964
TunneledTargetPort: s.port,
965
TunneledClients: map[string]uint32{},
966
}
967
}
968
portsItems := []*gitpod.PortsItems{}
969
for _, port := range tt.fields.orderInYaml {
970
portsItems = append(portsItems, &gitpod.PortsItems{Port: port})
971
}
972
portsConfig, rangeConfig := parseInstanceConfigs(portsItems)
973
pm := &Manager{
974
configs: &Configs{
975
instancePortConfigs: portsConfig,
976
instanceRangeConfigs: rangeConfig,
977
},
978
state: state,
979
}
980
got := pm.getStatus()
981
if len(got) != len(tt.want) {
982
t.Errorf("Manager.getStatus() length = %v, want %v", len(got), len(tt.want))
983
}
984
gotPorts := []uint32{}
985
for _, g := range got {
986
gotPorts = append(gotPorts, g.LocalPort)
987
}
988
if diff := cmp.Diff(gotPorts, tt.want); diff != "" {
989
t.Errorf("unexpected exposures (-want +got):\n%s", diff)
990
}
991
})
992
}
993
}
994
995