diff --git a/agent/consul/intention_endpoint.go b/agent/consul/intention_endpoint.go index f363cafd3..208315d77 100644 --- a/agent/consul/intention_endpoint.go +++ b/agent/consul/intention_endpoint.go @@ -127,6 +127,9 @@ func (s *Intention) Apply( // Validate. We do not validate on delete since it is valid to only // send an ID in that case. if args.Op != structs.IntentionOpDelete { + // Set the precedence + args.Intention.UpdatePrecedence() + if err := args.Intention.Validate(); err != nil { return err } diff --git a/agent/consul/intention_endpoint_test.go b/agent/consul/intention_endpoint_test.go index 29db41f44..c70dc57a3 100644 --- a/agent/consul/intention_endpoint_test.go +++ b/agent/consul/intention_endpoint_test.go @@ -67,6 +67,7 @@ func TestIntentionApply_new(t *testing.T) { actual.CreateIndex, actual.ModifyIndex = 0, 0 actual.CreatedAt = ixn.Intention.CreatedAt actual.UpdatedAt = ixn.Intention.UpdatedAt + ixn.Intention.UpdatePrecedence() assert.Equal(ixn.Intention, actual) } } @@ -201,7 +202,7 @@ func TestIntentionApply_updateGood(t *testing.T) { // Update ixn.Op = structs.IntentionOpUpdate ixn.Intention.ID = reply - ixn.Intention.SourceName = "bar" + ixn.Intention.SourceName = "*" assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply)) // Read @@ -221,6 +222,7 @@ func TestIntentionApply_updateGood(t *testing.T) { actual.CreateIndex, actual.ModifyIndex = 0, 0 actual.CreatedAt = ixn.Intention.CreatedAt actual.UpdatedAt = ixn.Intention.UpdatedAt + ixn.Intention.UpdatePrecedence() assert.Equal(ixn.Intention, actual) } } @@ -378,6 +380,7 @@ service "foo" { actual.CreateIndex, actual.ModifyIndex = 0, 0 actual.CreatedAt = ixn.Intention.CreatedAt actual.UpdatedAt = ixn.Intention.UpdatedAt + ixn.Intention.UpdatePrecedence() assert.Equal(ixn.Intention, actual) } } diff --git a/agent/structs/intention.go b/agent/structs/intention.go index 19a6402ab..f9df47b6a 100644 --- a/agent/structs/intention.go +++ b/agent/structs/intention.go @@ -61,6 +61,11 @@ type Intention struct { // opaque to Consul but is served in API responses. Meta map[string]string + // Precedence is the order that the intention will be applied, with + // larger numbers being applied first. This is a read-only field, on + // any intention update it is updated. + Precedence int + // CreatedAt and UpdatedAt keep track of when this record was created // or modified. CreatedAt, UpdatedAt time.Time `mapstructure:"-"` @@ -160,6 +165,49 @@ func (x *Intention) Validate() error { return result } +// UpdatePrecedence sets the Precedence value based on the fields of this +// structure. +func (x *Intention) UpdatePrecedence() { + // Max maintains the maximum value that the precedence can be depending + // on the number of exact values in the destination. + var max int + switch x.countExact(x.DestinationNS, x.DestinationName) { + case 2: + max = 9 + case 1: + max = 6 + case 0: + max = 3 + default: + // This shouldn't be possible, just set it to zero + x.Precedence = 0 + return + } + + // Given the maximum, the exact value is determined based on the + // number of source exact values. + countSrc := x.countExact(x.SourceNS, x.SourceName) + x.Precedence = max - (2 - countSrc) +} + +// countExact counts the number of exact values (not wildcards) in +// the given namespace and name. +func (x *Intention) countExact(ns, n string) int { + // If NS is wildcard, it must be zero since wildcards only follow exact + if ns == IntentionWildcard { + return 0 + } + + // Same reasoning as above, a wildcard can only follow an exact value + // and an exact value cannot follow a wildcard, so if name is a wildcard + // we must have exactly one. + if n == IntentionWildcard { + return 1 + } + + return 2 +} + // GetACLPrefix returns the prefix to look up the ACL policy for this // intention, and a boolean noting whether the prefix is valid to check // or not. You must check the ok value before using the prefix. @@ -354,20 +402,8 @@ func (s IntentionPrecedenceSorter) Swap(i, j int) { func (s IntentionPrecedenceSorter) Less(i, j int) bool { a, b := s[i], s[j] - - // First test the # of exact values in destination, since precedence - // is destination-oriented. - aExact := s.countExact(a.DestinationNS, a.DestinationName) - bExact := s.countExact(b.DestinationNS, b.DestinationName) - if aExact != bExact { - return aExact > bExact - } - - // Next test the # of exact values in source - aExact = s.countExact(a.SourceNS, a.SourceName) - bExact = s.countExact(b.SourceNS, b.SourceName) - if aExact != bExact { - return aExact > bExact + if a.Precedence != b.Precedence { + return a.Precedence > b.Precedence } // Tie break on lexicographic order of the 4-tuple in canonical form (SrcNS, @@ -387,21 +423,3 @@ func (s IntentionPrecedenceSorter) Less(i, j int) bool { } return a.DestinationName < b.DestinationName } - -// countExact counts the number of exact values (not wildcards) in -// the given namespace and name. -func (s IntentionPrecedenceSorter) countExact(ns, n string) int { - // If NS is wildcard, it must be zero since wildcards only follow exact - if ns == IntentionWildcard { - return 0 - } - - // Same reasoning as above, a wildcard can only follow an exact value - // and an exact value cannot follow a wildcard, so if name is a wildcard - // we must have exactly one. - if n == IntentionWildcard { - return 1 - } - - return 2 -} diff --git a/agent/structs/intention_test.go b/agent/structs/intention_test.go index cda88632f..92c8e567f 100644 --- a/agent/structs/intention_test.go +++ b/agent/structs/intention_test.go @@ -232,6 +232,11 @@ func TestIntentionPrecedenceSorter(t *testing.T) { }) } + // Set all the precedence values + for _, ixn := range input { + ixn.UpdatePrecedence() + } + // Sort sort.Sort(IntentionPrecedenceSorter(input))