fsm: add Intention operations to transactions for internal use

This commit is contained in:
Kyle Havlovitz 2018-10-18 05:45:37 -07:00
parent 405db688f8
commit 6f40708aca
4 changed files with 154 additions and 3 deletions

View File

@ -110,6 +110,18 @@ func (s *Store) txnKVS(tx *memdb.Txn, idx uint64, op *structs.TxnKVOp) (structs.
return nil, nil
}
// txnIntention handles all Intention-related operations.
func (s *Store) txnIntention(tx *memdb.Txn, idx uint64, op *structs.TxnIntentionOp) error {
switch op.Op {
case structs.IntentionOpCreate, structs.IntentionOpUpdate:
return s.intentionSetTxn(tx, idx, op.Intention)
case structs.IntentionOpDelete:
return s.intentionDeleteTxn(tx, idx, op.Intention.ID)
default:
return fmt.Errorf("unknown Intention verb %q", op.Op)
}
}
// txnDispatch runs the given operations inside the state store transaction.
func (s *Store) txnDispatch(tx *memdb.Txn, idx uint64, ops structs.TxnOps) (structs.TxnResults, structs.TxnErrors) {
results := make(structs.TxnResults, 0, len(ops))
@ -119,9 +131,12 @@ func (s *Store) txnDispatch(tx *memdb.Txn, idx uint64, ops structs.TxnOps) (stru
var err error
// Dispatch based on the type of operation.
if op.KV != nil {
switch {
case op.KV != nil:
ret, err = s.txnKVS(tx, idx, op.KV)
} else {
case op.Intention != nil:
err = s.txnIntention(tx, idx, op.Intention)
default:
err = fmt.Errorf("no operation specified")
}

View File

@ -7,8 +7,115 @@ import (
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/pascaldekloe/goe/verify"
"github.com/stretchr/testify/require"
)
func TestStateStore_Txn_Intention(t *testing.T) {
require := require.New(t)
s := testStateStore(t)
// Create some intentions.
ixn1 := &structs.Intention{
ID: testUUID(),
SourceNS: "default",
SourceName: "web",
DestinationNS: "default",
DestinationName: "db",
Meta: map[string]string{},
}
ixn2 := &structs.Intention{
ID: testUUID(),
SourceNS: "default",
SourceName: "db",
DestinationNS: "default",
DestinationName: "*",
Action: structs.IntentionActionDeny,
Meta: map[string]string{},
}
ixn3 := &structs.Intention{
ID: testUUID(),
SourceNS: "default",
SourceName: "foo",
DestinationNS: "default",
DestinationName: "*",
Meta: map[string]string{},
}
// Write the first two to the state store, leave the third
// to be created by the transaction operation.
require.NoError(s.IntentionSet(1, ixn1))
require.NoError(s.IntentionSet(2, ixn2))
// Set up a transaction that hits every operation.
ops := structs.TxnOps{
&structs.TxnOp{
Intention: &structs.TxnIntentionOp{
Op: structs.IntentionOpUpdate,
Intention: ixn1,
},
},
&structs.TxnOp{
Intention: &structs.TxnIntentionOp{
Op: structs.IntentionOpDelete,
Intention: ixn2,
},
},
&structs.TxnOp{
Intention: &structs.TxnIntentionOp{
Op: structs.IntentionOpCreate,
Intention: ixn3,
},
},
}
results, errors := s.TxnRW(3, ops)
if len(errors) > 0 {
t.Fatalf("err: %v", errors)
}
// Make sure the response looks as expected.
expected := structs.TxnResults{}
verify.Values(t, "", results, expected)
// Pull the resulting state store contents.
idx, actual, err := s.Intentions(nil)
require.NoError(err)
if idx != 3 {
t.Fatalf("bad index: %d", idx)
}
// Make sure it looks as expected.
intentions := structs.Intentions{
&structs.Intention{
ID: ixn1.ID,
SourceNS: "default",
SourceName: "web",
DestinationNS: "default",
DestinationName: "db",
Meta: map[string]string{},
Precedence: 9,
RaftIndex: structs.RaftIndex{
CreateIndex: 1,
ModifyIndex: 3,
},
},
&structs.Intention{
ID: ixn3.ID,
SourceNS: "default",
SourceName: "foo",
DestinationNS: "default",
DestinationName: "*",
Meta: map[string]string{},
Precedence: 6,
RaftIndex: structs.RaftIndex{
CreateIndex: 3,
ModifyIndex: 3,
},
},
}
verify.Values(t, "", actual, intentions)
}
func TestStateStore_Txn_KVS(t *testing.T) {
s := testStateStore(t)

View File

@ -224,6 +224,19 @@ func (x *Intention) String() string {
x.ID, x.Precedence)
}
// EstimateSize returns an estimate (in bytes) of the size of this structure when encoded.
func (x *Intention) EstimateSize() int {
// 60 = 36 (uuid) + 16 (RaftIndex) + 4 (Precedence) + 4 (DefaultPort)
size := 60 + len(x.Description) + len(x.SourceNS) + len(x.SourceName) + len(x.DestinationNS) +
len(x.DestinationName) + len(x.SourceType) + len(x.Action) + len(x.DefaultAddr)
for k, v := range x.Meta {
size += len(k) + len(v)
}
return size
}
// IntentionAction is the action that the intention represents. This
// can be "allow" or "deny" to whitelist or blacklist intentions.
type IntentionAction string

View File

@ -1,9 +1,11 @@
package structs
import (
"errors"
"fmt"
"github.com/hashicorp/consul/api"
multierror "github.com/hashicorp/go-multierror"
)
// TxnKVOp is used to define a single operation on the KVS inside a
@ -17,10 +19,15 @@ type TxnKVOp struct {
// inside a transaction.
type TxnKVResult *DirEntry
// TxnKVOp is used to define a single operation on an Intention inside a
// transaction.
type TxnIntentionOp IntentionRequest
// TxnOp is used to define a single operation inside a transaction. Only one
// of the types should be filled out per entry.
type TxnOp struct {
KV *TxnKVOp
KV *TxnKVOp
Intention *TxnIntentionOp
}
// TxnOps is a list of operations within a transaction.
@ -80,6 +87,15 @@ type TxnResponse struct {
Errors TxnErrors
}
// Error returns an aggregate of all errors in this TxnResponse.
func (r TxnResponse) Error() error {
var errs error
for _, err := range r.Errors {
errs = multierror.Append(errs, errors.New(err.Error()))
}
return errs
}
// TxnReadResponse is the structure returned by a TxnReadRequest.
type TxnReadResponse struct {
TxnResponse