Skip to content

Commit 14dcfc8

Browse files
committed
Merge branch 'main' into emotional-damage-8
2 parents d84bf88 + 5284d97 commit 14dcfc8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1905
-1211
lines changed

cli/portforward.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func (r *RootCmd) portForward() *clibase.Cmd {
9898
return xerrors.Errorf("await agent: %w", err)
9999
}
100100

101-
var logger slog.Logger
101+
logger := slog.Make()
102102
if r.verbose {
103103
logger = slog.Make(sloghuman.Sink(inv.Stdout)).Leveled(slog.LevelDebug)
104104
}
@@ -131,7 +131,7 @@ func (r *RootCmd) portForward() *clibase.Cmd {
131131
defer closeAllListeners()
132132

133133
for i, spec := range specs {
134-
l, err := listenAndPortForward(ctx, inv, conn, wg, spec)
134+
l, err := listenAndPortForward(ctx, inv, conn, wg, spec, logger)
135135
if err != nil {
136136
return err
137137
}
@@ -185,7 +185,15 @@ func (r *RootCmd) portForward() *clibase.Cmd {
185185
return cmd
186186
}
187187

188-
func listenAndPortForward(ctx context.Context, inv *clibase.Invocation, conn *codersdk.WorkspaceAgentConn, wg *sync.WaitGroup, spec portForwardSpec) (net.Listener, error) {
188+
func listenAndPortForward(
189+
ctx context.Context,
190+
inv *clibase.Invocation,
191+
conn *codersdk.WorkspaceAgentConn,
192+
wg *sync.WaitGroup,
193+
spec portForwardSpec,
194+
logger slog.Logger,
195+
) (net.Listener, error) {
196+
logger = logger.With(slog.F("network", spec.listenNetwork), slog.F("address", spec.listenAddress))
189197
_, _ = fmt.Fprintf(inv.Stderr, "Forwarding '%v://%v' locally to '%v://%v' in the workspace\n", spec.listenNetwork, spec.listenAddress, spec.dialNetwork, spec.dialAddress)
190198

191199
var (
@@ -218,6 +226,7 @@ func listenAndPortForward(ctx context.Context, inv *clibase.Invocation, conn *co
218226
if err != nil {
219227
return nil, xerrors.Errorf("listen '%v://%v': %w", spec.listenNetwork, spec.listenAddress, err)
220228
}
229+
logger.Debug(ctx, "listening")
221230

222231
wg.Add(1)
223232
go func(spec portForwardSpec) {
@@ -227,12 +236,14 @@ func listenAndPortForward(ctx context.Context, inv *clibase.Invocation, conn *co
227236
if err != nil {
228237
// Silently ignore net.ErrClosed errors.
229238
if xerrors.Is(err, net.ErrClosed) {
239+
logger.Debug(ctx, "listener closed")
230240
return
231241
}
232242
_, _ = fmt.Fprintf(inv.Stderr, "Error accepting connection from '%v://%v': %v\n", spec.listenNetwork, spec.listenAddress, err)
233243
_, _ = fmt.Fprintln(inv.Stderr, "Killing listener")
234244
return
235245
}
246+
logger.Debug(ctx, "accepted connection", slog.F("remote_addr", netConn.RemoteAddr()))
236247

237248
go func(netConn net.Conn) {
238249
defer netConn.Close()
@@ -242,8 +253,10 @@ func listenAndPortForward(ctx context.Context, inv *clibase.Invocation, conn *co
242253
return
243254
}
244255
defer remoteConn.Close()
256+
logger.Debug(ctx, "dialed remote", slog.F("remote_addr", netConn.RemoteAddr()))
245257

246258
agentssh.Bicopy(ctx, netConn, remoteConn)
259+
logger.Debug(ctx, "connection closing", slog.F("remote_addr", netConn.RemoteAddr()))
247260
}(netConn)
248261
}
249262
}(spec)

cli/portforward_test.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,10 @@ func TestPortForward(t *testing.T) {
140140

141141
for _, c := range cases {
142142
c := c
143-
// Delay parallel tests here because setupLocal reserves
143+
// No parallel tests here because setupLocal reserves
144144
// a free open port which is not guaranteed to be free
145145
// between the listener closing and port-forward ready.
146+
//nolint:tparallel,paralleltest
146147
t.Run(c.name+"_OnePort", func(t *testing.T) {
147148
p1 := setupTestListener(t, c.setupRemote(t))
148149

@@ -166,8 +167,6 @@ func TestPortForward(t *testing.T) {
166167
}()
167168
pty.ExpectMatchContext(ctx, "Ready!")
168169

169-
t.Parallel() // Port is reserved, enable parallel execution.
170-
171170
// Open two connections simultaneously and test them out of
172171
// sync.
173172
d := net.Dialer{Timeout: testutil.WaitShort}
@@ -185,6 +184,10 @@ func TestPortForward(t *testing.T) {
185184
require.ErrorIs(t, err, context.Canceled)
186185
})
187186

187+
// No parallel tests here because setupLocal reserves
188+
// a free open port which is not guaranteed to be free
189+
// between the listener closing and port-forward ready.
190+
//nolint:tparallel,paralleltest
188191
t.Run(c.name+"_TwoPorts", func(t *testing.T) {
189192
var (
190193
p1 = setupTestListener(t, c.setupRemote(t))
@@ -213,8 +216,6 @@ func TestPortForward(t *testing.T) {
213216
}()
214217
pty.ExpectMatchContext(ctx, "Ready!")
215218

216-
t.Parallel() // Port is reserved, enable parallel execution.
217-
218219
// Open a connection to both listener 1 and 2 simultaneously and
219220
// then test them out of order.
220221
d := net.Dialer{Timeout: testutil.WaitShort}
@@ -234,6 +235,10 @@ func TestPortForward(t *testing.T) {
234235
}
235236

236237
// Test doing TCP and UDP at the same time.
238+
// No parallel tests here because setupLocal reserves
239+
// a free open port which is not guaranteed to be free
240+
// between the listener closing and port-forward ready.
241+
//nolint:tparallel,paralleltest
237242
t.Run("All", func(t *testing.T) {
238243
var (
239244
dials = []addr{}
@@ -266,8 +271,6 @@ func TestPortForward(t *testing.T) {
266271
}()
267272
pty.ExpectMatchContext(ctx, "Ready!")
268273

269-
t.Parallel() // Port is reserved, enable parallel execution.
270-
271274
// Open connections to all items in the "dial" array.
272275
var (
273276
d = net.Dialer{Timeout: testutil.WaitShort}

coderd/autobuild/lifecycle_executor_test.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,6 @@ func TestExecutorAutostopTemplateDisabled(t *testing.T) {
743743

744744
// Given: we have a workspace built from a template that disallows user autostop
745745
var (
746-
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
747746
tickCh = make(chan time.Time)
748747
statsCh = make(chan autobuild.Stats)
749748

@@ -761,26 +760,38 @@ func TestExecutorAutostopTemplateDisabled(t *testing.T) {
761760
},
762761
},
763762
})
764-
// Given: we have a user with a workspace configured to autostart some time in the future
763+
// Given: we have a user with a workspace configured to autostop 30 minutes in the future
765764
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
766-
cwr.TTLMillis = ptr.Ref(8 * time.Hour.Milliseconds())
765+
cwr.TTLMillis = ptr.Ref(30 * time.Minute.Milliseconds())
767766
})
768767
)
769768

770769
// When: we create the workspace
771770
// Then: the deadline should be set to the template default TTL
772771
assert.WithinDuration(t, workspace.LatestBuild.CreatedAt.Add(time.Hour), workspace.LatestBuild.Deadline.Time, time.Minute)
773772

774-
// When: the autobuild executor ticks before the next scheduled time
773+
// When: the autobuild executor ticks after the workspace setting, but before the template setting:
775774
go func() {
776-
tickCh <- sched.Next(workspace.LatestBuild.CreatedAt).Add(time.Minute)
777-
close(tickCh)
775+
tickCh <- workspace.LatestBuild.CreatedAt.Add(45 * time.Minute)
778776
}()
779777

780778
// Then: nothing should happen
781779
stats := <-statsCh
782780
assert.NoError(t, stats.Error)
783781
assert.Len(t, stats.Transitions, 0)
782+
783+
// When: the autobuild executor ticks after the template setting:
784+
go func() {
785+
tickCh <- workspace.LatestBuild.CreatedAt.Add(61 * time.Minute)
786+
close(tickCh)
787+
}()
788+
789+
// Then: the workspace should be stopped
790+
stats = <-statsCh
791+
assert.NoError(t, stats.Error)
792+
assert.Len(t, stats.Transitions, 1)
793+
assert.Contains(t, stats.Transitions, workspace.ID)
794+
assert.Equal(t, database.WorkspaceTransitionStop, stats.Transitions[workspace.ID])
784795
}
785796

786797
// Test that an AGPL AccessControlStore properly disables

coderd/database/dump.sql

Lines changed: 67 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/foreign_key_constraint.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
BEGIN;
2+
3+
DROP TRIGGER IF EXISTS tailnet_notify_tunnel_change ON tailnet_tunnels;
4+
DROP FUNCTION IF EXISTS tailnet_notify_tunnel_change;
5+
DROP TABLE IF EXISTS tailnet_tunnels;
6+
7+
DROP TRIGGER IF EXISTS tailnet_notify_peer_change ON tailnet_peers;
8+
DROP FUNCTION IF EXISTS tailnet_notify_peer_change;
9+
DROP INDEX IF EXISTS idx_tailnet_peers_coordinator;
10+
DROP TABLE IF EXISTS tailnet_peers;
11+
12+
DROP TYPE IF EXISTS tailnet_status;
13+
14+
COMMIT;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
BEGIN;
2+
3+
CREATE TYPE tailnet_status AS ENUM (
4+
'ok',
5+
'lost'
6+
);
7+
8+
CREATE TABLE tailnet_peers (
9+
id uuid NOT NULL,
10+
coordinator_id uuid NOT NULL,
11+
updated_at timestamp with time zone NOT NULL,
12+
node bytea NOT NULL,
13+
status tailnet_status DEFAULT 'ok'::tailnet_status NOT NULL,
14+
PRIMARY KEY (id, coordinator_id),
15+
FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE
16+
);
17+
18+
-- For shutting down / GC a coordinator
19+
CREATE INDEX idx_tailnet_peers_coordinator ON tailnet_peers (coordinator_id);
20+
21+
-- Any time tailnet_peers table changes, send an update with the affected peer ID.
22+
CREATE FUNCTION tailnet_notify_peer_change() RETURNS trigger
23+
LANGUAGE plpgsql
24+
AS $$
25+
BEGIN
26+
IF (OLD IS NOT NULL) THEN
27+
PERFORM pg_notify('tailnet_peer_update', OLD.id::text);
28+
RETURN NULL;
29+
END IF;
30+
IF (NEW IS NOT NULL) THEN
31+
PERFORM pg_notify('tailnet_peer_update', NEW.id::text);
32+
RETURN NULL;
33+
END IF;
34+
END;
35+
$$;
36+
37+
CREATE TRIGGER tailnet_notify_peer_change
38+
AFTER INSERT OR UPDATE OR DELETE ON tailnet_peers
39+
FOR EACH ROW
40+
EXECUTE PROCEDURE tailnet_notify_peer_change();
41+
42+
CREATE TABLE tailnet_tunnels (
43+
coordinator_id uuid NOT NULL,
44+
-- we don't keep foreign keys for src_id and dst_id because the coordinator doesn't
45+
-- strictly order creating the peers and creating the tunnels
46+
src_id uuid NOT NULL,
47+
dst_id uuid NOT NULL,
48+
updated_at timestamp with time zone NOT NULL,
49+
PRIMARY KEY (coordinator_id, src_id, dst_id),
50+
FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators (id) ON DELETE CASCADE
51+
);
52+
53+
CREATE FUNCTION tailnet_notify_tunnel_change() RETURNS trigger
54+
LANGUAGE plpgsql
55+
AS $$
56+
BEGIN
57+
IF (NEW IS NOT NULL) THEN
58+
PERFORM pg_notify('tailnet_tunnel_update', NEW.src_id || ',' || NEW.dst_id);
59+
RETURN NULL;
60+
ELSIF (OLD IS NOT NULL) THEN
61+
PERFORM pg_notify('tailnet_tunnel_update', OLD.src_id || ',' || OLD.dst_id);
62+
RETURN NULL;
63+
END IF;
64+
END;
65+
$$;
66+
67+
CREATE TRIGGER tailnet_notify_tunnel_change
68+
AFTER INSERT OR UPDATE OR DELETE ON tailnet_tunnels
69+
FOR EACH ROW
70+
EXECUTE PROCEDURE tailnet_notify_tunnel_change();
71+
72+
COMMIT;

0 commit comments

Comments
 (0)