open-consul/agent/consul/state/acl_events_test.go
Daniel Nephin aa571bd0ce state: Move change processing out of EventPublisher
EventPublisher was receiving TopicHandlers, which had a couple of
problems:

- ChangeProcessors were being grouped by Topic, but they completely
  ignored the topic and were performed on every change
- ChangeProcessors required EventPublisher to be aware of database
  changes

By moving ChangeProcesors out of EventPublisher, and having Publish
accept events instead of changes, EventPublisher no longer needs to
be aware of these things.

Handlers is now only SnapshotHandlers, which are still mapped by Topic.

Also allows us to remove the small 'db' package that had only two types.
They can now be unexported types in state.
2020-07-14 15:57:47 -04:00

204 lines
6.2 KiB
Go

package state
import (
"strconv"
"strings"
"testing"
"github.com/hashicorp/consul/agent/consul/stream"
"github.com/hashicorp/consul/agent/structs"
"github.com/stretchr/testify/require"
)
func TestACLChangeUnsubscribeEvent(t *testing.T) {
cases := []struct {
Name string
Setup func(s *Store, tx *txn) error
Mutate func(s *Store, tx *txn) error
expected stream.Event
}{
{
Name: "token create",
Mutate: func(s *Store, tx *txn) error {
return s.aclTokenSetTxn(tx, tx.Index, newACLToken(1), false, false, false, false)
},
expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
},
{
Name: "token update",
Setup: func(s *Store, tx *txn) error {
return s.aclTokenSetTxn(tx, tx.Index, newACLToken(1), false, false, false, false)
},
Mutate: func(s *Store, tx *txn) error {
// Add a policy to the token (never mind it doesn't exist for now) we
// allow it in the set command below.
token := newACLToken(1)
token.Policies = []structs.ACLTokenPolicyLink{{ID: "33333333-1111-1111-1111-111111111111"}}
return s.aclTokenSetTxn(tx, tx.Index, token, false, true, false, false)
},
expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
},
{
Name: "token delete",
Setup: func(s *Store, tx *txn) error {
return s.aclTokenSetTxn(tx, tx.Index, newACLToken(1), false, false, false, false)
},
Mutate: func(s *Store, tx *txn) error {
token := newACLToken(1)
return s.aclTokenDeleteTxn(tx, tx.Index, token.AccessorID, "id", nil)
},
expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
},
{
Name: "policy create",
Mutate: newACLPolicyWithSingleToken,
// two identical tokens, because Mutate has two changes
expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1, 1)),
},
{
Name: "policy update",
Setup: newACLPolicyWithSingleToken,
Mutate: func(s *Store, tx *txn) error {
policy := newACLPolicy(1)
policy.Rules = `operator = "write"`
return s.aclPolicySetTxn(tx, tx.Index, policy)
},
expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
},
{
Name: "policy delete",
Setup: newACLPolicyWithSingleToken,
Mutate: func(s *Store, tx *txn) error {
policy := newACLPolicy(1)
return s.aclPolicyDeleteTxn(tx, tx.Index, policy.ID, s.aclPolicyGetByID, nil)
},
expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
},
{
Name: "role create",
Mutate: newACLRoleWithSingleToken,
// Two tokens with the same ID, because there are two changes in Mutate
expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1, 1)),
},
{
Name: "role update",
Setup: newACLRoleWithSingleToken,
Mutate: func(s *Store, tx *txn) error {
role := newACLRole(1, newACLRolePolicyLink(1))
policy2 := newACLPolicy(2)
role.Policies = append(role.Policies, structs.ACLRolePolicyLink{
ID: policy2.ID,
Name: policy2.Name,
})
return s.aclRoleSetTxn(tx, tx.Index, role, true)
},
expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
},
{
Name: "role delete",
Setup: newACLRoleWithSingleToken,
Mutate: func(s *Store, tx *txn) error {
role := newACLRole(1, newACLRolePolicyLink(1))
return s.aclRoleDeleteTxn(tx, tx.Index, role.ID, s.aclRoleGetByID, nil)
},
expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
s := testStateStore(t)
if tc.Setup != nil {
// Bypass the publish mechanism for this test or we get into odd
// recursive stuff...
setupTx := s.db.WriteTxn(10)
require.NoError(t, tc.Setup(s, setupTx))
// Commit the underlying transaction without using wrapped Commit so we
// avoid the whole event publishing system for setup here. It _should_
// work but it makes debugging test hard as it will call the function
// under test for the setup data...
setupTx.Txn.Commit()
}
tx := s.db.WriteTxn(100)
require.NoError(t, tc.Mutate(s, tx))
// Note we call the func under test directly rather than publishChanges so
// we can test this in isolation.
events, err := aclChangeUnsubscribeEvent(tx, Changes{Index: 100, Changes: tx.Changes()})
require.NoError(t, err)
require.Len(t, events, 1)
actual := events[0]
require.Equal(t, tc.expected, actual)
})
}
}
func newACLRoleWithSingleToken(s *Store, tx *txn) error {
role := newACLRole(1, newACLRolePolicyLink(1))
if err := s.aclRoleSetTxn(tx, tx.Index, role, true); err != nil {
return err
}
token := newACLToken(1)
token.Roles = append(token.Roles, structs.ACLTokenRoleLink{ID: role.ID})
return s.aclTokenSetTxn(tx, tx.Index, token, false, false, false, false)
}
func newACLPolicyWithSingleToken(s *Store, tx *txn) error {
policy := newACLPolicy(1)
if err := s.aclPolicySetTxn(tx, tx.Index, policy); err != nil {
return err
}
token := newACLToken(1)
token.Policies = append(token.Policies, structs.ACLTokenPolicyLink{ID: policy.ID})
return s.aclTokenSetTxn(tx, tx.Index, token, false, false, false, false)
}
func newSecretIDs(ids ...int) []string {
result := make([]string, 0, len(ids))
for _, id := range ids {
uuid := strings.ReplaceAll("11111111-????-????-????-????????????", "?", strconv.Itoa(id))
result = append(result, uuid)
}
return result
}
func newACLToken(n int) *structs.ACLToken {
uuid := strings.ReplaceAll("11111111-????-????-????-????????????", "?", strconv.Itoa(n))
return &structs.ACLToken{
AccessorID: uuid,
SecretID: uuid,
}
}
func newACLPolicy(n int) *structs.ACLPolicy {
numStr := strconv.Itoa(n)
uuid := strings.ReplaceAll("22222222-????-????-????-????????????", "?", numStr)
return &structs.ACLPolicy{
ID: uuid,
Name: "test_policy_" + numStr,
Rules: `operator = "read"`,
}
}
func newACLRole(n int, policies ...structs.ACLRolePolicyLink) *structs.ACLRole {
numStr := strconv.Itoa(n)
uuid := strings.ReplaceAll("33333333-????-????-????-????????????", "?", numStr)
return &structs.ACLRole{
ID: uuid,
Name: "test_role_" + numStr,
Policies: policies,
}
}
func newACLRolePolicyLink(n int) structs.ACLRolePolicyLink {
policy := newACLPolicy(n)
return structs.ACLRolePolicyLink{
ID: policy.ID,
Name: policy.Name,
}
}