agent/consul/fsm,state: snapshot/restore for CA roots

This commit is contained in:
Mitchell Hashimoto 2018-03-21 11:33:19 -07:00
parent 17d6b437d2
commit 2dfca5dbc2
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
4 changed files with 104 additions and 57 deletions

View File

@ -21,6 +21,7 @@ func init() {
registerRestorer(structs.PreparedQueryRequestType, restorePreparedQuery) registerRestorer(structs.PreparedQueryRequestType, restorePreparedQuery)
registerRestorer(structs.AutopilotRequestType, restoreAutopilot) registerRestorer(structs.AutopilotRequestType, restoreAutopilot)
registerRestorer(structs.IntentionRequestType, restoreIntention) registerRestorer(structs.IntentionRequestType, restoreIntention)
registerRestorer(structs.ConnectCARequestType, restoreConnectCA)
} }
func persistOSS(s *snapshot, sink raft.SnapshotSink, encoder *codec.Encoder) error { func persistOSS(s *snapshot, sink raft.SnapshotSink, encoder *codec.Encoder) error {
@ -48,6 +49,9 @@ func persistOSS(s *snapshot, sink raft.SnapshotSink, encoder *codec.Encoder) err
if err := s.persistIntentions(sink, encoder); err != nil { if err := s.persistIntentions(sink, encoder); err != nil {
return err return err
} }
if err := s.persistConnectCA(sink, encoder); err != nil {
return err
}
return nil return nil
} }
@ -262,6 +266,24 @@ func (s *snapshot) persistAutopilot(sink raft.SnapshotSink,
return nil return nil
} }
func (s *snapshot) persistConnectCA(sink raft.SnapshotSink,
encoder *codec.Encoder) error {
roots, err := s.state.CARoots()
if err != nil {
return err
}
for _, r := range roots {
if _, err := sink.Write([]byte{byte(structs.ConnectCARequestType)}); err != nil {
return err
}
if err := encoder.Encode(r); err != nil {
return err
}
}
return nil
}
func (s *snapshot) persistIntentions(sink raft.SnapshotSink, func (s *snapshot) persistIntentions(sink raft.SnapshotSink,
encoder *codec.Encoder) error { encoder *codec.Encoder) error {
ixns, err := s.state.Intentions() ixns, err := s.state.Intentions()
@ -397,3 +419,14 @@ func restoreIntention(header *snapshotHeader, restore *state.Restore, decoder *c
} }
return nil return nil
} }
func restoreConnectCA(header *snapshotHeader, restore *state.Restore, decoder *codec.Decoder) error {
var req structs.CARoot
if err := decoder.Decode(&req); err != nil {
return err
}
if err := restore.CARoot(&req); err != nil {
return err
}
return nil
}

View File

@ -7,6 +7,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/consul/autopilot" "github.com/hashicorp/consul/agent/consul/autopilot"
"github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/consul/state"
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
@ -110,6 +111,18 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
} }
assert.Nil(fsm.state.IntentionSet(14, ixn)) assert.Nil(fsm.state.IntentionSet(14, ixn))
// CA Roots
roots := []*structs.CARoot{
connect.TestCA(t, nil),
connect.TestCA(t, nil),
}
for _, r := range roots[1:] {
r.Active = false
}
ok, err := fsm.state.CARootSetCAS(15, 0, roots)
assert.Nil(err)
assert.True(ok)
// Snapshot // Snapshot
snap, err := fsm.Snapshot() snap, err := fsm.Snapshot()
if err != nil { if err != nil {
@ -278,6 +291,11 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
assert.Len(ixns, 1) assert.Len(ixns, 1)
assert.Equal(ixn, ixns[0]) assert.Equal(ixn, ixns[0])
// Verify CA roots are restored.
_, roots, err = fsm2.state.CARoots(nil)
assert.Nil(err)
assert.Len(roots, 2)
// Snapshot // Snapshot
snap, err = fsm2.Snapshot() snap, err = fsm2.Snapshot()
if err != nil { if err != nil {

View File

@ -33,6 +33,34 @@ func init() {
registerSchema(caRootTableSchema) registerSchema(caRootTableSchema)
} }
// CARoots is used to pull all the CA roots for the snapshot.
func (s *Snapshot) CARoots() (structs.CARoots, error) {
ixns, err := s.tx.Get(caRootTableName, "id")
if err != nil {
return nil, err
}
var ret structs.CARoots
for wrapped := ixns.Next(); wrapped != nil; wrapped = ixns.Next() {
ret = append(ret, wrapped.(*structs.CARoot))
}
return ret, nil
}
// CARoots is used when restoring from a snapshot.
func (s *Restore) CARoot(r *structs.CARoot) error {
// Insert
if err := s.tx.Insert(caRootTableName, r); err != nil {
return fmt.Errorf("failed restoring CA root: %s", err)
}
if err := indexUpdateMaxTxn(s.tx, r.ModifyIndex, caRootTableName); err != nil {
return fmt.Errorf("failed updating index: %s", err)
}
return nil
}
// CARoots returns the list of all CA roots. // CARoots returns the list of all CA roots.
func (s *Store) CARoots(ws memdb.WatchSet) (uint64, structs.CARoots, error) { func (s *Store) CARoots(ws memdb.WatchSet) (uint64, structs.CARoots, error) {
tx := s.db.Txn(false) tx := s.db.Txn(false)

View File

@ -113,92 +113,60 @@ func TestStore_CARootActive_none(t *testing.T) {
assert.Nil(err) assert.Nil(err)
} }
/* func TestStore_CARoot_Snapshot_Restore(t *testing.T) {
func TestStore_Intention_Snapshot_Restore(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
s := testStateStore(t) s := testStateStore(t)
// Create some intentions. // Create some intentions.
ixns := structs.Intentions{ roots := structs.CARoots{
&structs.Intention{ connect.TestCA(t, nil),
DestinationName: "foo", connect.TestCA(t, nil),
}, connect.TestCA(t, nil),
&structs.Intention{ }
DestinationName: "bar", for _, r := range roots[1:] {
}, r.Active = false
&structs.Intention{
DestinationName: "baz",
},
} }
// Force the sort order of the UUIDs before we create them so the // Force the sort order of the UUIDs before we create them so the
// order is deterministic. // order is deterministic.
id := testUUID() id := testUUID()
ixns[0].ID = "a" + id[1:] roots[0].ID = "a" + id[1:]
ixns[1].ID = "b" + id[1:] roots[1].ID = "b" + id[1:]
ixns[2].ID = "c" + id[1:] roots[2].ID = "c" + id[1:]
// Now create // Now create
for i, ixn := range ixns { ok, err := s.CARootSetCAS(1, 0, roots)
assert.Nil(s.IntentionSet(uint64(4+i), ixn)) assert.Nil(err)
} assert.True(ok)
// Snapshot the queries. // Snapshot the queries.
snap := s.Snapshot() snap := s.Snapshot()
defer snap.Close() defer snap.Close()
// Alter the real state store. // Alter the real state store.
assert.Nil(s.IntentionDelete(7, ixns[0].ID)) ok, err = s.CARootSetCAS(2, 1, roots[:1])
assert.Nil(err)
assert.True(ok)
// Verify the snapshot. // Verify the snapshot.
assert.Equal(snap.LastIndex(), uint64(6)) assert.Equal(snap.LastIndex(), uint64(1))
expected := structs.Intentions{ dump, err := snap.CARoots()
&structs.Intention{
ID: ixns[0].ID,
DestinationName: "foo",
Meta: map[string]string{},
RaftIndex: structs.RaftIndex{
CreateIndex: 4,
ModifyIndex: 4,
},
},
&structs.Intention{
ID: ixns[1].ID,
DestinationName: "bar",
Meta: map[string]string{},
RaftIndex: structs.RaftIndex{
CreateIndex: 5,
ModifyIndex: 5,
},
},
&structs.Intention{
ID: ixns[2].ID,
DestinationName: "baz",
Meta: map[string]string{},
RaftIndex: structs.RaftIndex{
CreateIndex: 6,
ModifyIndex: 6,
},
},
}
dump, err := snap.Intentions()
assert.Nil(err) assert.Nil(err)
assert.Equal(expected, dump) assert.Equal(roots, dump)
// Restore the values into a new state store. // Restore the values into a new state store.
func() { func() {
s := testStateStore(t) s := testStateStore(t)
restore := s.Restore() restore := s.Restore()
for _, ixn := range dump { for _, r := range dump {
assert.Nil(restore.Intention(ixn)) assert.Nil(restore.CARoot(r))
} }
restore.Commit() restore.Commit()
// Read the restored values back out and verify that they match. // Read the restored values back out and verify that they match.
idx, actual, err := s.Intentions(nil) idx, actual, err := s.CARoots(nil)
assert.Nil(err) assert.Nil(err)
assert.Equal(idx, uint64(6)) assert.Equal(idx, uint64(2))
assert.Equal(expected, actual) assert.Equal(roots, actual)
}() }()
} }
*/