|
|
@@ -4,10 +4,14 @@
|
|
|
package appc
|
|
|
|
|
|
import (
|
|
|
+ "encoding/json"
|
|
|
"net/netip"
|
|
|
+ "reflect"
|
|
|
"testing"
|
|
|
|
|
|
"tailscale.com/tailcfg"
|
|
|
+ "tailscale.com/types/appctype"
|
|
|
+ "tailscale.com/types/opt"
|
|
|
)
|
|
|
|
|
|
// TestHandleConnectorTransitIPRequestZeroLength tests that if sent a
|
|
|
@@ -186,3 +190,122 @@ func TestTransitIPTargetUnknownTIP(t *testing.T) {
|
|
|
t.Fatalf("Unknown transit addr, want: %v, got %v", want, got)
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+func TestPickSplitDNSPeers(t *testing.T) {
|
|
|
+ getBytesForAttr := func(name string, domains []string, tags []string) []byte {
|
|
|
+ attr := appctype.AppConnectorAttr{
|
|
|
+ Name: name,
|
|
|
+ Domains: domains,
|
|
|
+ Connectors: tags,
|
|
|
+ }
|
|
|
+ bs, err := json.Marshal(attr)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("test setup: %v", err)
|
|
|
+ }
|
|
|
+ return bs
|
|
|
+ }
|
|
|
+ appOneBytes := getBytesForAttr("app1", []string{"example.com"}, []string{"tag:one"})
|
|
|
+ appTwoBytes := getBytesForAttr("app2", []string{"a.example.com"}, []string{"tag:two"})
|
|
|
+ appThreeBytes := getBytesForAttr("app3", []string{"woo.b.example.com", "hoo.b.example.com"}, []string{"tag:three1", "tag:three2"})
|
|
|
+ appFourBytes := getBytesForAttr("app4", []string{"woo.b.example.com", "c.example.com"}, []string{"tag:four1", "tag:four2"})
|
|
|
+
|
|
|
+ makeNodeView := func(id tailcfg.NodeID, name string, tags []string) tailcfg.NodeView {
|
|
|
+ return (&tailcfg.Node{
|
|
|
+ ID: id,
|
|
|
+ Name: name,
|
|
|
+ Tags: tags,
|
|
|
+ Hostinfo: (&tailcfg.Hostinfo{AppConnector: opt.NewBool(true)}).View(),
|
|
|
+ }).View()
|
|
|
+ }
|
|
|
+ nvp1 := makeNodeView(1, "p1", []string{"tag:one"})
|
|
|
+ nvp2 := makeNodeView(2, "p2", []string{"tag:four1", "tag:four2"})
|
|
|
+ nvp3 := makeNodeView(3, "p3", []string{"tag:two", "tag:three1"})
|
|
|
+ nvp4 := makeNodeView(4, "p4", []string{"tag:two", "tag:three2", "tag:four2"})
|
|
|
+
|
|
|
+ for _, tt := range []struct {
|
|
|
+ name string
|
|
|
+ want map[string][]tailcfg.NodeView
|
|
|
+ peers []tailcfg.NodeView
|
|
|
+ config []tailcfg.RawMessage
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "empty",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "bad-config", // bad config should return a nil map rather than error.
|
|
|
+ config: []tailcfg.RawMessage{tailcfg.RawMessage(`hey`)},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "no-peers",
|
|
|
+ config: []tailcfg.RawMessage{tailcfg.RawMessage(appOneBytes)},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "peers-that-are-not-connectors",
|
|
|
+ config: []tailcfg.RawMessage{tailcfg.RawMessage(appOneBytes)},
|
|
|
+ peers: []tailcfg.NodeView{
|
|
|
+ (&tailcfg.Node{
|
|
|
+ ID: 5,
|
|
|
+ Name: "p5",
|
|
|
+ Tags: []string{"tag:one"},
|
|
|
+ }).View(),
|
|
|
+ (&tailcfg.Node{
|
|
|
+ ID: 6,
|
|
|
+ Name: "p6",
|
|
|
+ Tags: []string{"tag:one"},
|
|
|
+ }).View(),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "peers-that-dont-match-tags",
|
|
|
+ config: []tailcfg.RawMessage{tailcfg.RawMessage(appOneBytes)},
|
|
|
+ peers: []tailcfg.NodeView{
|
|
|
+ makeNodeView(5, "p5", []string{"tag:seven"}),
|
|
|
+ makeNodeView(6, "p6", nil),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "matching-tagged-connector-peers",
|
|
|
+ config: []tailcfg.RawMessage{
|
|
|
+ tailcfg.RawMessage(appOneBytes),
|
|
|
+ tailcfg.RawMessage(appTwoBytes),
|
|
|
+ tailcfg.RawMessage(appThreeBytes),
|
|
|
+ tailcfg.RawMessage(appFourBytes),
|
|
|
+ },
|
|
|
+ peers: []tailcfg.NodeView{
|
|
|
+ nvp1,
|
|
|
+ nvp2,
|
|
|
+ nvp3,
|
|
|
+ nvp4,
|
|
|
+ makeNodeView(5, "p5", nil),
|
|
|
+ },
|
|
|
+ want: map[string][]tailcfg.NodeView{
|
|
|
+ // p5 has no matching tags and so doesn't appear
|
|
|
+ "example.com": {nvp1},
|
|
|
+ "a.example.com": {nvp3, nvp4},
|
|
|
+ "woo.b.example.com": {nvp2, nvp3, nvp4},
|
|
|
+ "hoo.b.example.com": {nvp3, nvp4},
|
|
|
+ "c.example.com": {nvp2, nvp4},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ } {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ selfNode := &tailcfg.Node{}
|
|
|
+ if tt.config != nil {
|
|
|
+ selfNode.CapMap = tailcfg.NodeCapMap{
|
|
|
+ tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): tt.config,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ selfView := selfNode.View()
|
|
|
+ peers := map[tailcfg.NodeID]tailcfg.NodeView{}
|
|
|
+ for _, p := range tt.peers {
|
|
|
+ peers[p.ID()] = p
|
|
|
+ }
|
|
|
+ got := PickSplitDNSPeers(func(_ tailcfg.NodeCapability) bool {
|
|
|
+ return true
|
|
|
+ }, selfView, peers)
|
|
|
+ if !reflect.DeepEqual(got, tt.want) {
|
|
|
+ t.Fatalf("got %v, want %v", got, tt.want)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|