agent/consul: RPC endpoint for Intention.Match

This commit is contained in:
Mitchell Hashimoto 2018-03-02 13:40:03 -08:00
parent f93edadbbe
commit 93de03fe8b
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
3 changed files with 116 additions and 5 deletions

View File

@ -142,3 +142,33 @@ func (s *Intention) List(
}, },
) )
} }
// Match returns the set of intentions that match the given source/destination.
func (s *Intention) Match(
args *structs.IntentionQueryRequest,
reply *structs.IndexedIntentionMatches) error {
// Forward if necessary
if done, err := s.srv.forward("Intention.Match", args, args, reply); done {
return err
}
// TODO(mitchellh): validate
return s.srv.blockingQuery(
&args.QueryOptions,
&reply.QueryMeta,
func(ws memdb.WatchSet, state *state.Store) error {
index, matches, err := state.IntentionMatch(ws, args.Match)
if err != nil {
return err
}
reply.Index = index
reply.Matches = matches
// TODO(mitchellh): acl filtering
return nil
},
)
}

View File

@ -258,3 +258,79 @@ func TestIntentionList(t *testing.T) {
} }
} }
} }
// Test basic matching. We don't need to exhaustively test inputs since this
// is tested in the agent/consul/state package.
func TestIntentionMatch_good(t *testing.T) {
t.Parallel()
dir1, s1 := testServer(t)
defer os.RemoveAll(dir1)
defer s1.Shutdown()
codec := rpcClient(t, s1)
defer codec.Close()
testrpc.WaitForLeader(t, s1.RPC, "dc1")
// Create some records
{
insert := [][]string{
{"foo", "*"},
{"foo", "bar"},
{"foo", "baz"}, // shouldn't match
{"bar", "bar"}, // shouldn't match
{"bar", "*"}, // shouldn't match
{"*", "*"},
}
for _, v := range insert {
ixn := structs.IntentionRequest{
Datacenter: "dc1",
Op: structs.IntentionOpCreate,
Intention: &structs.Intention{
SourceNS: "default",
SourceName: "test",
DestinationNS: v[0],
DestinationName: v[1],
},
}
// Create
var reply string
if err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply); err != nil {
t.Fatalf("err: %v", err)
}
}
}
// Match
req := &structs.IntentionQueryRequest{
Datacenter: "dc1",
Match: &structs.IntentionQueryMatch{
Type: structs.IntentionMatchDestination,
Entries: []structs.IntentionMatchEntry{
{
Namespace: "foo",
Name: "bar",
},
},
},
}
var resp structs.IndexedIntentionMatches
if err := msgpackrpc.CallWithCodec(codec, "Intention.Match", req, &resp); err != nil {
t.Fatalf("err: %v", err)
}
if len(resp.Matches) != 1 {
t.Fatalf("bad: %#v", resp.Matches)
}
expected := [][]string{{"foo", "bar"}, {"foo", "*"}, {"*", "*"}}
var actual [][]string
for _, ixn := range resp.Matches[0] {
actual = append(actual, []string{ixn.DestinationNS, ixn.DestinationName})
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad (got, wanted):\n\n%#v\n\n%#v", actual, expected)
}
}

View File

@ -75,6 +75,12 @@ type IndexedIntentions struct {
QueryMeta QueryMeta
} }
// IndexedIntentionMatches represents the list of matches for a match query.
type IndexedIntentionMatches struct {
Matches []Intentions
QueryMeta
}
// IntentionOp is the operation for a request related to intentions. // IntentionOp is the operation for a request related to intentions.
type IntentionOp string type IntentionOp string
@ -123,11 +129,10 @@ type IntentionQueryRequest struct {
// IntentionID is the ID of a specific intention. // IntentionID is the ID of a specific intention.
IntentionID string IntentionID string
// MatchBy and MatchNames are used to match a namespace/name pair // Match is non-nil if we're performing a match query. A match will
// to a set of intentions. The list of MatchNames is an OR list, // find intentions that "match" the given parameters. A match includes
// all matching intentions are returned together. // resolving wildcards.
MatchBy IntentionMatchType Match *IntentionQueryMatch
MatchNames []string
// Options for queries // Options for queries
QueryOptions QueryOptions