Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit15157c1

Browse files
authored
chore: add network integration test suite scaffolding (#13072)
* chore: add network integration test suite scaffolding* dean comments
1 parent73ba36c commit15157c1

File tree

2 files changed

+322
-0
lines changed

2 files changed

+322
-0
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"io"
7+
"net/http"
8+
"net/http/httptest"
9+
"net/netip"
10+
"strings"
11+
"sync/atomic"
12+
"testing"
13+
"time"
14+
15+
"github.com/google/uuid"
16+
"github.com/stretchr/testify/require"
17+
"golang.org/x/xerrors"
18+
"nhooyr.io/websocket"
19+
"tailscale.com/tailcfg"
20+
21+
"cdr.dev/slog"
22+
"github.com/coder/coder/v2/coderd/httpapi"
23+
"github.com/coder/coder/v2/codersdk"
24+
"github.com/coder/coder/v2/tailnet"
25+
"github.com/coder/coder/v2/testutil"
26+
)
27+
28+
funcNetworkSetupDefault(*testing.T) {}
29+
30+
funcDERPMapTailscale(ctx context.Context,t*testing.T)*tailcfg.DERPMap {
31+
ctx,cancel:=context.WithTimeout(ctx,testutil.WaitShort)
32+
defercancel()
33+
34+
req,err:=http.NewRequestWithContext(ctx,"GET","https://controlplane.tailscale.com/derpmap/default",nil)
35+
require.NoError(t,err)
36+
37+
res,err:=http.DefaultClient.Do(req)
38+
require.NoError(t,err)
39+
deferres.Body.Close()
40+
41+
dm:=&tailcfg.DERPMap{}
42+
dec:=json.NewDecoder(res.Body)
43+
err=dec.Decode(dm)
44+
require.NoError(t,err)
45+
46+
returndm
47+
}
48+
49+
funcCoordinatorInMemory(t*testing.T,logger slog.Logger,dm*tailcfg.DERPMap) (coord tailnet.Coordinator,urlstring) {
50+
coord=tailnet.NewCoordinator(logger)
51+
varcoordPtr atomic.Pointer[tailnet.Coordinator]
52+
coordPtr.Store(&coord)
53+
t.Cleanup(func() {_=coord.Close() })
54+
55+
csvc,err:=tailnet.NewClientService(logger,&coordPtr,10*time.Minute,func()*tailcfg.DERPMap {
56+
returndm
57+
})
58+
require.NoError(t,err)
59+
60+
srv:=httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter,r*http.Request) {
61+
idStr:=strings.TrimPrefix(r.URL.Path,"/")
62+
id,err:=uuid.Parse(idStr)
63+
iferr!=nil {
64+
httpapi.Write(r.Context(),w,http.StatusBadRequest, codersdk.Response{
65+
Message:"Bad agent id.",
66+
Detail:err.Error(),
67+
})
68+
return
69+
}
70+
71+
conn,err:=websocket.Accept(w,r,nil)
72+
iferr!=nil {
73+
httpapi.Write(r.Context(),w,http.StatusBadRequest, codersdk.Response{
74+
Message:"Failed to accept websocket.",
75+
Detail:err.Error(),
76+
})
77+
return
78+
}
79+
80+
ctx,wsNetConn:=codersdk.WebsocketNetConn(r.Context(),conn,websocket.MessageBinary)
81+
deferwsNetConn.Close()
82+
83+
err=csvc.ServeConnV2(ctx,wsNetConn, tailnet.StreamID{
84+
Name:"client-"+id.String(),
85+
ID:id,
86+
Auth: tailnet.SingleTailnetCoordinateeAuth{},
87+
})
88+
iferr!=nil&&!xerrors.Is(err,io.EOF)&&!xerrors.Is(err,context.Canceled) {
89+
_=conn.Close(websocket.StatusInternalError,err.Error())
90+
return
91+
}
92+
}))
93+
t.Cleanup(srv.Close)
94+
95+
returncoord,srv.URL
96+
}
97+
98+
funcTailnetSetupDRPC(ctx context.Context,t*testing.T,logger slog.Logger,
99+
id,agentID uuid.UUID,
100+
coordinateURLstring,
101+
dm*tailcfg.DERPMap,
102+
)*tailnet.Conn {
103+
ip:=tailnet.IPFromUUID(id)
104+
conn,err:=tailnet.NewConn(&tailnet.Options{
105+
Addresses: []netip.Prefix{netip.PrefixFrom(ip,128)},
106+
DERPMap:dm,
107+
Logger:logger,
108+
})
109+
require.NoError(t,err)
110+
t.Cleanup(func() {_=conn.Close() })
111+
112+
//nolint:bodyclose
113+
ws,_,err:=websocket.Dial(ctx,coordinateURL+"/"+id.String(),nil)
114+
require.NoError(t,err)
115+
116+
client,err:=tailnet.NewDRPCClient(
117+
websocket.NetConn(ctx,ws,websocket.MessageBinary),
118+
logger,
119+
)
120+
require.NoError(t,err)
121+
122+
coord,err:=client.Coordinate(ctx)
123+
require.NoError(t,err)
124+
125+
coordination:=tailnet.NewRemoteCoordination(logger,coord,conn,agentID)
126+
t.Cleanup(func() {_=coordination.Close() })
127+
returnconn
128+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"flag"
6+
"fmt"
7+
"os"
8+
"os/exec"
9+
"strconv"
10+
"syscall"
11+
"testing"
12+
13+
"github.com/google/uuid"
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
"tailscale.com/tailcfg"
17+
18+
"cdr.dev/slog"
19+
"cdr.dev/slog/sloggers/slogtest"
20+
"github.com/coder/coder/v2/tailnet"
21+
"github.com/coder/coder/v2/testutil"
22+
)
23+
24+
var (
25+
isChild=flag.Bool("child",false,"Run tests as a child")
26+
childTestID=flag.Int("child-test-id",0,"Which test is being run")
27+
childCoordinateURL=flag.String("child-coordinate-url","","The coordinate url to connect back to")
28+
childAgentID=flag.String("child-agent-id","","The agent id of the child")
29+
)
30+
31+
funcTestMain(m*testing.M) {
32+
ifrun:=os.Getenv("CODER_TAILNET_TESTS");run=="" {
33+
_,_=fmt.Println("skipping tests...")
34+
return
35+
}
36+
ifos.Getuid()!=0 {
37+
_,_=fmt.Println("networking integration tests must run as root")
38+
return
39+
}
40+
flag.Parse()
41+
os.Exit(m.Run())
42+
}
43+
44+
vartests= []Test{{
45+
Name:"Normal",
46+
DERPMap:DERPMapTailscale,
47+
Coordinator:CoordinatorInMemory,
48+
Parent:Parent{
49+
NetworkSetup:NetworkSetupDefault,
50+
TailnetSetup:TailnetSetupDRPC,
51+
Run:func(ctx context.Context,t*testing.T,optsParentOpts) {
52+
reach:=opts.Conn.AwaitReachable(ctx,tailnet.IPFromUUID(opts.AgentID))
53+
assert.True(t,reach)
54+
},
55+
},
56+
Child:Child{
57+
NetworkSetup:NetworkSetupDefault,
58+
TailnetSetup:TailnetSetupDRPC,
59+
Run:func(ctx context.Context,t*testing.T,optsChildOpts) {
60+
// wait until the parent kills us
61+
<-make(chanstruct{})
62+
},
63+
},
64+
}}
65+
66+
//nolint:paralleltest
67+
funcTestIntegration(t*testing.T) {
68+
if*isChild {
69+
logger:=slogtest.Make(t,nil).Leveled(slog.LevelDebug)
70+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitSuperLong)
71+
t.Cleanup(cancel)
72+
73+
agentID,err:=uuid.Parse(*childAgentID)
74+
require.NoError(t,err)
75+
76+
test:=tests[*childTestID]
77+
test.Child.NetworkSetup(t)
78+
dm:=test.DERPMap(ctx,t)
79+
conn:=test.Child.TailnetSetup(ctx,t,logger,agentID,uuid.Nil,*childCoordinateURL,dm)
80+
test.Child.Run(ctx,t,ChildOpts{
81+
Logger:logger,
82+
Conn:conn,
83+
AgentID:agentID,
84+
})
85+
return
86+
}
87+
88+
forid,test:=rangetests {
89+
t.Run(test.Name,func(t*testing.T) {
90+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitSuperLong)
91+
t.Cleanup(cancel)
92+
93+
logger:=slogtest.Make(t,nil).Leveled(slog.LevelDebug)
94+
parentID,childID:=uuid.New(),uuid.New()
95+
dm:=test.DERPMap(ctx,t)
96+
_,coordURL:=test.Coordinator(t,logger,dm)
97+
98+
child,waitChild:=execChild(ctx,id,coordURL,childID)
99+
test.Parent.NetworkSetup(t)
100+
conn:=test.Parent.TailnetSetup(ctx,t,logger,parentID,childID,coordURL,dm)
101+
test.Parent.Run(ctx,t,ParentOpts{
102+
Logger:logger,
103+
Conn:conn,
104+
ClientID:parentID,
105+
AgentID:childID,
106+
})
107+
child.Process.Signal(syscall.SIGINT)
108+
<-waitChild
109+
})
110+
}
111+
}
112+
113+
typeTeststruct {
114+
// Name is the name of the test.
115+
Namestring
116+
117+
// DERPMap returns the DERP map to use for both the parent and child. It is
118+
// called once at the beginning of the test.
119+
DERPMapfunc(ctx context.Context,t*testing.T)*tailcfg.DERPMap
120+
// Coordinator returns a running tailnet coordinator, and the url to reach
121+
// it on.
122+
Coordinatorfunc(t*testing.T,logger slog.Logger,dm*tailcfg.DERPMap) (coord tailnet.Coordinator,urlstring)
123+
124+
ParentParent
125+
ChildChild
126+
}
127+
128+
// Parent is the struct containing all of the parent specific configurations.
129+
// Functions are invoked in order of struct definition.
130+
typeParentstruct {
131+
// NetworkSetup is run before all test code. It can be used to setup
132+
// networking scenarios.
133+
NetworkSetupfunc(t*testing.T)
134+
135+
// TailnetSetup creates a tailnet network.
136+
TailnetSetupfunc(
137+
ctx context.Context,t*testing.T,logger slog.Logger,
138+
id,agentID uuid.UUID,coordURLstring,dm*tailcfg.DERPMap,
139+
)*tailnet.Conn
140+
141+
Runfunc(ctx context.Context,t*testing.T,optsParentOpts)
142+
}
143+
144+
// Child is the struct containing all of the child specific configurations.
145+
// Functions are invoked in order of struct definition.
146+
typeChildstruct {
147+
// NetworkSetup is run before all test code. It can be used to setup
148+
// networking scenarios.
149+
NetworkSetupfunc(t*testing.T)
150+
151+
// TailnetSetup creates a tailnet network.
152+
TailnetSetupfunc(
153+
ctx context.Context,t*testing.T,logger slog.Logger,
154+
id,agentID uuid.UUID,coordURLstring,dm*tailcfg.DERPMap,
155+
)*tailnet.Conn
156+
157+
// Run runs the actual test. Parents and children run in separate processes,
158+
// so it's important to ensure no communication happens over memory between
159+
// run functions of parents and children.
160+
Runfunc(ctx context.Context,t*testing.T,optsChildOpts)
161+
}
162+
163+
typeParentOptsstruct {
164+
Logger slog.Logger
165+
Conn*tailnet.Conn
166+
ClientID uuid.UUID
167+
AgentID uuid.UUID
168+
}
169+
170+
typeChildOptsstruct {
171+
Logger slog.Logger
172+
Conn*tailnet.Conn
173+
AgentID uuid.UUID
174+
}
175+
176+
funcexecChild(ctx context.Context,testIDint,coordURLstring,agentID uuid.UUID) (*exec.Cmd,<-chanerror) {
177+
ch:=make(chanerror)
178+
binary:=os.Args[0]
179+
args:=os.Args[1:]
180+
args=append(args,
181+
"--child=true",
182+
"--child-test-id="+strconv.Itoa(testID),
183+
"--child-coordinate-url="+coordURL,
184+
"--child-agent-id="+agentID.String(),
185+
)
186+
187+
cmd:=exec.CommandContext(ctx,binary,args...)
188+
cmd.Stdout=os.Stdout
189+
cmd.Stderr=os.Stderr
190+
gofunc() {
191+
ch<-cmd.Run()
192+
}()
193+
returncmd,ch
194+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp