// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package state import ( "testing" "github.com/hashicorp/go-memdb" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" "github.com/shoenig/test/must" ) func TestStateStore_UpsertACLBindingRules(t *testing.T) { ci.Parallel(t) testState := testStateStore(t) // Generate a mocked ACL binding rule for testing and attempt to upsert // this straight into state. It should fail because the auth method does // not exist. mockedACLBindingRules := []*structs.ACLBindingRule{mock.ACLBindingRule()} err := testState.UpsertACLBindingRules(10, mockedACLBindingRules, false) must.EqError(t, err, "ACL binding rule insert failed: ACL auth method not found") // Create an auth method and ensure the binding rule is updated, so it is // related to it. authMethod := mock.ACLOIDCAuthMethod() mockedACLBindingRules[0].AuthMethod = authMethod.Name must.NoError(t, testState.UpsertACLAuthMethods(10, []*structs.ACLAuthMethod{authMethod})) must.NoError(t, testState.UpsertACLBindingRules(20, mockedACLBindingRules, false)) // Check that the index for the table was modified as expected. initialIndex, err := testState.Index(TableACLBindingRules) must.NoError(t, err) must.Eq(t, 20, initialIndex) // List all the ACL binding rules in the table, so we can perform a number // of tests on the return array. ws := memdb.NewWatchSet() iter, err := testState.GetACLBindingRules(ws) must.NoError(t, err) // Count how many table entries we have, to ensure it is the expected // number. var count int for raw := iter.Next(); raw != nil; raw = iter.Next() { count++ // Ensure the create and modify indexes are populated correctly. aclRole := raw.(*structs.ACLBindingRule) must.Eq(t, 20, aclRole.CreateIndex) must.Eq(t, 20, aclRole.ModifyIndex) } must.Eq(t, 1, count) // Try writing the same ACL binding rule to state which should not result // in an update to the table index. must.NoError(t, testState.UpsertACLBindingRules(20, mockedACLBindingRules, false)) reInsertActualIndex, err := testState.Index(TableACLBindingRules) must.NoError(t, err) must.Eq(t, 20, reInsertActualIndex) // Make a change to the binding rule and ensure this update is accepted and // the table index is updated. updatedMockedACLBindingRule := mockedACLBindingRules[0].Copy() updatedMockedACLBindingRule.BindType = "role-name" updatedMockedACLBindingRule.SetHash() must.NoError(t, testState.UpsertACLBindingRules( 30, []*structs.ACLBindingRule{updatedMockedACLBindingRule}, false)) // Check that the index for the table was modified as expected. updatedIndex, err := testState.Index(TableACLBindingRules) must.NoError(t, err) must.Eq(t, 30, updatedIndex) // List the ACL roles in state. iter, err = testState.GetACLBindingRules(ws) must.NoError(t, err) // Count how many table entries we have, to ensure it is the expected // number. count = 0 for raw := iter.Next(); raw != nil; raw = iter.Next() { count++ // Ensure the create and modify indexes are populated correctly. aclRole := raw.(*structs.ACLBindingRule) must.Eq(t, 20, aclRole.CreateIndex) must.Eq(t, 30, aclRole.ModifyIndex) } must.Eq(t, 1, count) // Now try inserting an ACL binding rule using the missing auth methods // argument to simulate replication. replicatedACLBindingRule := []*structs.ACLBindingRule{mock.ACLBindingRule()} must.NoError(t, testState.UpsertACLBindingRules(40, replicatedACLBindingRule, true)) replicatedACLBindingRuleResp, err := testState.GetACLBindingRule(ws, replicatedACLBindingRule[0].ID) must.NoError(t, err) must.Eq(t, replicatedACLBindingRule[0].Hash, replicatedACLBindingRuleResp.Hash) } func TestStateStore_DeleteACLBindingRules(t *testing.T) { ci.Parallel(t) testState := testStateStore(t) // Generate a some mocked ACL binding rules for testing and upsert these // straight into state. mockedACLBindingRoles := []*structs.ACLBindingRule{mock.ACLBindingRule(), mock.ACLBindingRule()} must.NoError(t, testState.UpsertACLBindingRules(10, mockedACLBindingRoles, true)) // Try and delete a binding rule using an ID that doesn't exist. This // should return an error and not change the index for the table. err := testState.DeleteACLBindingRules(20, []string{uuid.Generate()}) must.EqError(t, err, "ACL binding rule not found") tableIndex, err := testState.Index(TableACLBindingRules) must.NoError(t, err) must.Eq(t, 10, tableIndex) // Delete one of the previously upserted ACL binding rules. This should // succeed and modify the table index. must.NoError(t, testState.DeleteACLBindingRules(20, []string{mockedACLBindingRoles[0].ID})) tableIndex, err = testState.Index(TableACLBindingRules) must.NoError(t, err) must.Eq(t, 20, tableIndex) // List the ACL binding rules and ensure we now only have one present and // that it is the one we expect. ws := memdb.NewWatchSet() iter, err := testState.GetACLBindingRules(ws) must.NoError(t, err) var aclBindingRules []*structs.ACLBindingRule for raw := iter.Next(); raw != nil; raw = iter.Next() { aclBindingRules = append(aclBindingRules, raw.(*structs.ACLBindingRule)) } must.Len(t, 1, aclBindingRules) must.True(t, aclBindingRules[0].Equal(mockedACLBindingRoles[1])) // Delete the final remaining ACL binding rule. This should succeed and // modify the table index. must.NoError(t, testState.DeleteACLBindingRules(30, []string{mockedACLBindingRoles[1].ID})) tableIndex, err = testState.Index(TableACLBindingRules) must.NoError(t, err) must.Eq(t, 30, tableIndex) // List the ACL binding rules and ensure we have zero entries. iter, err = testState.GetACLBindingRules(ws) must.NoError(t, err) aclBindingRules = make([]*structs.ACLBindingRule, 0) for raw := iter.Next(); raw != nil; raw = iter.Next() { aclBindingRules = append(aclBindingRules, raw.(*structs.ACLBindingRule)) } must.Len(t, 0, aclBindingRules) } func TestStateStore_GetACLBindingRules(t *testing.T) { ci.Parallel(t) testState := testStateStore(t) // Generate a some mocked ACL binding rules for testing and upsert these // straight into state. mockedACLBindingRoles := []*structs.ACLBindingRule{mock.ACLBindingRule(), mock.ACLBindingRule()} must.NoError(t, testState.UpsertACLBindingRules(10, mockedACLBindingRoles, true)) // List the ACL binding rules and ensure they are exactly as we expect. ws := memdb.NewWatchSet() iter, err := testState.GetACLBindingRules(ws) must.NoError(t, err) var aclBindingRules []*structs.ACLBindingRule for raw := iter.Next(); raw != nil; raw = iter.Next() { aclBindingRules = append(aclBindingRules, raw.(*structs.ACLBindingRule)) } expected := mockedACLBindingRoles for i := range expected { expected[i].CreateIndex = 10 expected[i].ModifyIndex = 10 } must.SliceContainsAll(t, aclBindingRules, expected) } func TestStateStore_GetACLBindingRule(t *testing.T) { ci.Parallel(t) testState := testStateStore(t) // Generate a some mocked ACL binding rules for testing and upsert these // straight into state. mockedACLBindingRoles := []*structs.ACLBindingRule{mock.ACLBindingRule(), mock.ACLBindingRule()} must.NoError(t, testState.UpsertACLBindingRules(10, mockedACLBindingRoles, true)) ws := memdb.NewWatchSet() // Try reading an ACL binding rule that does not exist. aclBindingRule, err := testState.GetACLBindingRule(ws, uuid.Generate()) must.NoError(t, err) must.Nil(t, aclBindingRule) // Read the two ACL binding rules that we should find. aclBindingRule, err = testState.GetACLBindingRule(ws, mockedACLBindingRoles[0].ID) must.NoError(t, err) must.Eq(t, mockedACLBindingRoles[0], aclBindingRule) aclBindingRule, err = testState.GetACLBindingRule(ws, mockedACLBindingRoles[1].ID) must.NoError(t, err) must.Eq(t, mockedACLBindingRoles[1], aclBindingRule) } func TestStateStore_GetACLBindingRulesByAuthMethod(t *testing.T) { ci.Parallel(t) testState := testStateStore(t) // Generate a some mocked ACL binding rules for testing and upsert these // straight into state. mockedACLBindingRoles := []*structs.ACLBindingRule{mock.ACLBindingRule(), mock.ACLBindingRule()} must.NoError(t, testState.UpsertACLBindingRules(10, mockedACLBindingRoles, true)) ws := memdb.NewWatchSet() // Lookup ACL binding rules using an auth method that is not referenced. We // should not get any results within the iterator. iter, err := testState.GetACLBindingRulesByAuthMethod(ws, "not-an-auth-method") must.NoError(t, err) var aclBindingRules []*structs.ACLBindingRule for raw := iter.Next(); raw != nil; raw = iter.Next() { aclBindingRules = append(aclBindingRules, raw.(*structs.ACLBindingRule)) } must.Len(t, 0, aclBindingRules) // Lookup ACL binding rules using an auth method that is referenced by both // mocked rules. Ensure the results are as expected. iter, err = testState.GetACLBindingRulesByAuthMethod(ws, mockedACLBindingRoles[0].AuthMethod) must.NoError(t, err) aclBindingRules = make([]*structs.ACLBindingRule, 0) for raw := iter.Next(); raw != nil; raw = iter.Next() { aclBindingRules = append(aclBindingRules, raw.(*structs.ACLBindingRule)) } must.Len(t, 2, aclBindingRules) }