github.com/clly/consul@v1.4.5/agent/consul/fsm/snapshot_oss_test.go (about)

     1  package fsm
     2  
     3  import (
     4  	"bytes"
     5  	"os"
     6  	"reflect"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/hashicorp/consul/acl"
    11  	"github.com/hashicorp/consul/agent/connect"
    12  	"github.com/hashicorp/consul/agent/consul/autopilot"
    13  	"github.com/hashicorp/consul/agent/consul/state"
    14  	"github.com/hashicorp/consul/agent/structs"
    15  	"github.com/hashicorp/consul/api"
    16  	"github.com/hashicorp/consul/lib"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  func TestFSM_SnapshotRestore_OSS(t *testing.T) {
    22  	t.Parallel()
    23  
    24  	assert := assert.New(t)
    25  	fsm, err := New(nil, os.Stderr)
    26  	if err != nil {
    27  		t.Fatalf("err: %v", err)
    28  	}
    29  
    30  	// Add some state
    31  	node1 := &structs.Node{
    32  		ID:         "610918a6-464f-fa9b-1a95-03bd6e88ed92",
    33  		Node:       "foo",
    34  		Datacenter: "dc1",
    35  		Address:    "127.0.0.1",
    36  	}
    37  	node2 := &structs.Node{
    38  		ID:         "40e4a748-2192-161a-0510-9bf59fe950b5",
    39  		Node:       "baz",
    40  		Datacenter: "dc1",
    41  		Address:    "127.0.0.2",
    42  		TaggedAddresses: map[string]string{
    43  			"hello": "1.2.3.4",
    44  		},
    45  		Meta: map[string]string{
    46  			"testMeta": "testing123",
    47  		},
    48  	}
    49  	assert.NoError(fsm.state.EnsureNode(1, node1))
    50  	assert.NoError(fsm.state.EnsureNode(2, node2))
    51  
    52  	// Add a service instance with Connect config.
    53  	connectConf := structs.ServiceConnect{
    54  		Native: true,
    55  		Proxy: &structs.ServiceDefinitionConnectProxy{
    56  			Command:  []string{"foo", "bar"},
    57  			ExecMode: "a",
    58  			Config: map[string]interface{}{
    59  				"a": "qwer",
    60  				"b": 4.3,
    61  			},
    62  		},
    63  	}
    64  	fsm.state.EnsureService(3, "foo", &structs.NodeService{
    65  		ID:      "web",
    66  		Service: "web",
    67  		Tags:    nil,
    68  		Address: "127.0.0.1",
    69  		Port:    80,
    70  		Connect: connectConf,
    71  	})
    72  	fsm.state.EnsureService(4, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"primary"}, Address: "127.0.0.1", Port: 5000})
    73  	fsm.state.EnsureService(5, "baz", &structs.NodeService{ID: "web", Service: "web", Tags: nil, Address: "127.0.0.2", Port: 80})
    74  	fsm.state.EnsureService(6, "baz", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"secondary"}, Address: "127.0.0.2", Port: 5000})
    75  	fsm.state.EnsureCheck(7, &structs.HealthCheck{
    76  		Node:      "foo",
    77  		CheckID:   "web",
    78  		Name:      "web connectivity",
    79  		Status:    api.HealthPassing,
    80  		ServiceID: "web",
    81  	})
    82  	fsm.state.KVSSet(8, &structs.DirEntry{
    83  		Key:   "/test",
    84  		Value: []byte("foo"),
    85  	})
    86  	session := &structs.Session{ID: generateUUID(), Node: "foo"}
    87  	fsm.state.SessionCreate(9, session)
    88  	policy := structs.ACLPolicy{
    89  		ID:          structs.ACLPolicyGlobalManagementID,
    90  		Name:        "global-management",
    91  		Description: "Builtin Policy that grants unlimited access",
    92  		Rules:       structs.ACLPolicyGlobalManagement,
    93  		Syntax:      acl.SyntaxCurrent,
    94  	}
    95  	policy.SetHash(true)
    96  	require.NoError(t, fsm.state.ACLPolicySet(1, &policy))
    97  
    98  	token := &structs.ACLToken{
    99  		AccessorID:  "30fca056-9fbb-4455-b94a-bf0e2bc575d6",
   100  		SecretID:    "cbe1c6fd-d865-4034-9d6d-64fef7fb46a9",
   101  		Description: "Bootstrap Token (Global Management)",
   102  		Policies: []structs.ACLTokenPolicyLink{
   103  			{
   104  				ID: structs.ACLPolicyGlobalManagementID,
   105  			},
   106  		},
   107  		CreateTime: time.Now(),
   108  		Local:      false,
   109  		// DEPRECATED (ACL-Legacy-Compat) - This is used so that the bootstrap token is still visible via the v1 acl APIs
   110  		Type: structs.ACLTokenTypeManagement,
   111  	}
   112  	require.NoError(t, fsm.state.ACLBootstrap(10, 0, token, false))
   113  
   114  	fsm.state.KVSSet(11, &structs.DirEntry{
   115  		Key:   "/remove",
   116  		Value: []byte("foo"),
   117  	})
   118  	fsm.state.KVSDelete(12, "/remove")
   119  	idx, _, err := fsm.state.KVSList(nil, "/remove")
   120  	if err != nil {
   121  		t.Fatalf("err: %s", err)
   122  	}
   123  	if idx != 12 {
   124  		t.Fatalf("bad index: %d", idx)
   125  	}
   126  
   127  	updates := structs.Coordinates{
   128  		&structs.Coordinate{
   129  			Node:  "baz",
   130  			Coord: generateRandomCoordinate(),
   131  		},
   132  		&structs.Coordinate{
   133  			Node:  "foo",
   134  			Coord: generateRandomCoordinate(),
   135  		},
   136  	}
   137  	if err := fsm.state.CoordinateBatchUpdate(13, updates); err != nil {
   138  		t.Fatalf("err: %s", err)
   139  	}
   140  
   141  	query := structs.PreparedQuery{
   142  		ID: generateUUID(),
   143  		Service: structs.ServiceQuery{
   144  			Service: "web",
   145  		},
   146  		RaftIndex: structs.RaftIndex{
   147  			CreateIndex: 14,
   148  			ModifyIndex: 14,
   149  		},
   150  	}
   151  	if err := fsm.state.PreparedQuerySet(14, &query); err != nil {
   152  		t.Fatalf("err: %s", err)
   153  	}
   154  
   155  	autopilotConf := &autopilot.Config{
   156  		CleanupDeadServers:   true,
   157  		LastContactThreshold: 100 * time.Millisecond,
   158  		MaxTrailingLogs:      222,
   159  	}
   160  	if err := fsm.state.AutopilotSetConfig(15, autopilotConf); err != nil {
   161  		t.Fatalf("err: %s", err)
   162  	}
   163  
   164  	// Intentions
   165  	ixn := structs.TestIntention(t)
   166  	ixn.ID = generateUUID()
   167  	ixn.RaftIndex = structs.RaftIndex{
   168  		CreateIndex: 14,
   169  		ModifyIndex: 14,
   170  	}
   171  	assert.Nil(fsm.state.IntentionSet(14, ixn))
   172  
   173  	// CA Roots
   174  	roots := []*structs.CARoot{
   175  		connect.TestCA(t, nil),
   176  		connect.TestCA(t, nil),
   177  	}
   178  	for _, r := range roots[1:] {
   179  		r.Active = false
   180  	}
   181  	ok, err := fsm.state.CARootSetCAS(15, 0, roots)
   182  	assert.Nil(err)
   183  	assert.True(ok)
   184  
   185  	ok, err = fsm.state.CASetProviderState(16, &structs.CAConsulProviderState{
   186  		ID:         "asdf",
   187  		PrivateKey: "foo",
   188  		RootCert:   "bar",
   189  	})
   190  	assert.Nil(err)
   191  	assert.True(ok)
   192  
   193  	// CA Config
   194  	caConfig := &structs.CAConfiguration{
   195  		ClusterID: "foo",
   196  		Provider:  "consul",
   197  		Config: map[string]interface{}{
   198  			"foo": "asdf",
   199  			"bar": 6.5,
   200  		},
   201  	}
   202  	err = fsm.state.CASetConfig(17, caConfig)
   203  	assert.Nil(err)
   204  
   205  	// Snapshot
   206  	snap, err := fsm.Snapshot()
   207  	if err != nil {
   208  		t.Fatalf("err: %v", err)
   209  	}
   210  	defer snap.Release()
   211  
   212  	// Persist
   213  	buf := bytes.NewBuffer(nil)
   214  	sink := &MockSink{buf, false}
   215  	if err := snap.Persist(sink); err != nil {
   216  		t.Fatalf("err: %v", err)
   217  	}
   218  
   219  	// Try to restore on a new FSM
   220  	fsm2, err := New(nil, os.Stderr)
   221  	if err != nil {
   222  		t.Fatalf("err: %v", err)
   223  	}
   224  
   225  	// Do a restore
   226  	if err := fsm2.Restore(sink); err != nil {
   227  		t.Fatalf("err: %v", err)
   228  	}
   229  
   230  	// Verify the contents
   231  	_, nodes, err := fsm2.state.Nodes(nil)
   232  	if err != nil {
   233  		t.Fatalf("err: %s", err)
   234  	}
   235  	if len(nodes) != 2 {
   236  		t.Fatalf("bad: %v", nodes)
   237  	}
   238  	if nodes[0].ID != node2.ID ||
   239  		nodes[0].Node != "baz" ||
   240  		nodes[0].Datacenter != "dc1" ||
   241  		nodes[0].Address != "127.0.0.2" ||
   242  		len(nodes[0].Meta) != 1 ||
   243  		nodes[0].Meta["testMeta"] != "testing123" ||
   244  		len(nodes[0].TaggedAddresses) != 1 ||
   245  		nodes[0].TaggedAddresses["hello"] != "1.2.3.4" {
   246  		t.Fatalf("bad: %v", nodes[0])
   247  	}
   248  	if nodes[1].ID != node1.ID ||
   249  		nodes[1].Node != "foo" ||
   250  		nodes[1].Datacenter != "dc1" ||
   251  		nodes[1].Address != "127.0.0.1" ||
   252  		len(nodes[1].TaggedAddresses) != 0 {
   253  		t.Fatalf("bad: %v", nodes[1])
   254  	}
   255  
   256  	_, fooSrv, err := fsm2.state.NodeServices(nil, "foo")
   257  	if err != nil {
   258  		t.Fatalf("err: %s", err)
   259  	}
   260  	if len(fooSrv.Services) != 2 {
   261  		t.Fatalf("Bad: %v", fooSrv)
   262  	}
   263  	if !lib.StrContains(fooSrv.Services["db"].Tags, "primary") {
   264  		t.Fatalf("Bad: %v", fooSrv)
   265  	}
   266  	if fooSrv.Services["db"].Port != 5000 {
   267  		t.Fatalf("Bad: %v", fooSrv)
   268  	}
   269  	connectSrv := fooSrv.Services["web"]
   270  	if !reflect.DeepEqual(connectSrv.Connect, connectConf) {
   271  		t.Fatalf("got: %v, want: %v", connectSrv.Connect, connectConf)
   272  	}
   273  
   274  	_, checks, err := fsm2.state.NodeChecks(nil, "foo")
   275  	if err != nil {
   276  		t.Fatalf("err: %s", err)
   277  	}
   278  	if len(checks) != 1 {
   279  		t.Fatalf("Bad: %v", checks)
   280  	}
   281  
   282  	// Verify key is set
   283  	_, d, err := fsm2.state.KVSGet(nil, "/test")
   284  	if err != nil {
   285  		t.Fatalf("err: %v", err)
   286  	}
   287  	if string(d.Value) != "foo" {
   288  		t.Fatalf("bad: %v", d)
   289  	}
   290  
   291  	// Verify session is restored
   292  	idx, s, err := fsm2.state.SessionGet(nil, session.ID)
   293  	if err != nil {
   294  		t.Fatalf("err: %v", err)
   295  	}
   296  	if s.Node != "foo" {
   297  		t.Fatalf("bad: %v", s)
   298  	}
   299  	if idx <= 1 {
   300  		t.Fatalf("bad index: %d", idx)
   301  	}
   302  
   303  	// Verify ACL Token is restored
   304  	_, a, err := fsm2.state.ACLTokenGetByAccessor(nil, token.AccessorID)
   305  	require.NoError(t, err)
   306  	require.Equal(t, token.AccessorID, a.AccessorID)
   307  	require.Equal(t, token.ModifyIndex, a.ModifyIndex)
   308  
   309  	// Verify the acl-token-bootstrap index was restored
   310  	canBootstrap, index, err := fsm2.state.CanBootstrapACLToken()
   311  	require.False(t, canBootstrap)
   312  	require.True(t, index > 0)
   313  
   314  	// Verify ACL Policy is restored
   315  	_, policy2, err := fsm2.state.ACLPolicyGetByID(nil, structs.ACLPolicyGlobalManagementID)
   316  	require.NoError(t, err)
   317  	require.Equal(t, policy.Name, policy2.Name)
   318  
   319  	// Verify tombstones are restored
   320  	func() {
   321  		snap := fsm2.state.Snapshot()
   322  		defer snap.Close()
   323  		stones, err := snap.Tombstones()
   324  		if err != nil {
   325  			t.Fatalf("err: %s", err)
   326  		}
   327  		stone := stones.Next().(*state.Tombstone)
   328  		if stone == nil {
   329  			t.Fatalf("missing tombstone")
   330  		}
   331  		if stone.Key != "/remove" || stone.Index != 12 {
   332  			t.Fatalf("bad: %v", stone)
   333  		}
   334  		if stones.Next() != nil {
   335  			t.Fatalf("unexpected extra tombstones")
   336  		}
   337  	}()
   338  
   339  	// Verify coordinates are restored
   340  	_, coords, err := fsm2.state.Coordinates(nil)
   341  	if err != nil {
   342  		t.Fatalf("err: %s", err)
   343  	}
   344  	if !reflect.DeepEqual(coords, updates) {
   345  		t.Fatalf("bad: %#v", coords)
   346  	}
   347  
   348  	// Verify queries are restored.
   349  	_, queries, err := fsm2.state.PreparedQueryList(nil)
   350  	if err != nil {
   351  		t.Fatalf("err: %s", err)
   352  	}
   353  	if len(queries) != 1 {
   354  		t.Fatalf("bad: %#v", queries)
   355  	}
   356  	if !reflect.DeepEqual(queries[0], &query) {
   357  		t.Fatalf("bad: %#v", queries[0])
   358  	}
   359  
   360  	// Verify autopilot config is restored.
   361  	_, restoredConf, err := fsm2.state.AutopilotConfig()
   362  	if err != nil {
   363  		t.Fatalf("err: %s", err)
   364  	}
   365  	if !reflect.DeepEqual(restoredConf, autopilotConf) {
   366  		t.Fatalf("bad: %#v, %#v", restoredConf, autopilotConf)
   367  	}
   368  
   369  	// Verify intentions are restored.
   370  	_, ixns, err := fsm2.state.Intentions(nil)
   371  	assert.Nil(err)
   372  	assert.Len(ixns, 1)
   373  	assert.Equal(ixn, ixns[0])
   374  
   375  	// Verify CA roots are restored.
   376  	_, roots, err = fsm2.state.CARoots(nil)
   377  	assert.Nil(err)
   378  	assert.Len(roots, 2)
   379  
   380  	// Verify provider state is restored.
   381  	_, state, err := fsm2.state.CAProviderState("asdf")
   382  	assert.Nil(err)
   383  	assert.Equal("foo", state.PrivateKey)
   384  	assert.Equal("bar", state.RootCert)
   385  
   386  	// Verify CA configuration is restored.
   387  	_, caConf, err := fsm2.state.CAConfig()
   388  	assert.Nil(err)
   389  	assert.Equal(caConfig, caConf)
   390  
   391  	// Snapshot
   392  	snap, err = fsm2.Snapshot()
   393  	if err != nil {
   394  		t.Fatalf("err: %v", err)
   395  	}
   396  	defer snap.Release()
   397  
   398  	// Persist
   399  	buf = bytes.NewBuffer(nil)
   400  	sink = &MockSink{buf, false}
   401  	if err := snap.Persist(sink); err != nil {
   402  		t.Fatalf("err: %v", err)
   403  	}
   404  
   405  	// Try to restore on the old FSM and make sure it abandons the old state
   406  	// store.
   407  	abandonCh := fsm.state.AbandonCh()
   408  	if err := fsm.Restore(sink); err != nil {
   409  		t.Fatalf("err: %v", err)
   410  	}
   411  	select {
   412  	case <-abandonCh:
   413  	default:
   414  		t.Fatalf("bad")
   415  	}
   416  }
   417  
   418  func TestFSM_BadRestore_OSS(t *testing.T) {
   419  	t.Parallel()
   420  	// Create an FSM with some state.
   421  	fsm, err := New(nil, os.Stderr)
   422  	if err != nil {
   423  		t.Fatalf("err: %v", err)
   424  	}
   425  	fsm.state.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"})
   426  	abandonCh := fsm.state.AbandonCh()
   427  
   428  	// Do a bad restore.
   429  	buf := bytes.NewBuffer([]byte("bad snapshot"))
   430  	sink := &MockSink{buf, false}
   431  	if err := fsm.Restore(sink); err == nil {
   432  		t.Fatalf("err: %v", err)
   433  	}
   434  
   435  	// Verify the contents didn't get corrupted.
   436  	_, nodes, err := fsm.state.Nodes(nil)
   437  	if err != nil {
   438  		t.Fatalf("err: %s", err)
   439  	}
   440  	if len(nodes) != 1 {
   441  		t.Fatalf("bad: %v", nodes)
   442  	}
   443  	if nodes[0].Node != "foo" ||
   444  		nodes[0].Address != "127.0.0.1" ||
   445  		len(nodes[0].TaggedAddresses) != 0 {
   446  		t.Fatalf("bad: %v", nodes[0])
   447  	}
   448  
   449  	// Verify the old state store didn't get abandoned.
   450  	select {
   451  	case <-abandonCh:
   452  		t.Fatalf("bad")
   453  	default:
   454  	}
   455  }
   456  
   457  func TestFSM_BadSnapshot_NilCAConfig(t *testing.T) {
   458  	t.Parallel()
   459  
   460  	require := require.New(t)
   461  
   462  	// Create an FSM with no config entry.
   463  	fsm, err := New(nil, os.Stderr)
   464  	if err != nil {
   465  		t.Fatalf("err: %v", err)
   466  	}
   467  
   468  	// Snapshot
   469  	snap, err := fsm.Snapshot()
   470  	if err != nil {
   471  		t.Fatalf("err: %v", err)
   472  	}
   473  	defer snap.Release()
   474  
   475  	// Persist
   476  	buf := bytes.NewBuffer(nil)
   477  	sink := &MockSink{buf, false}
   478  	if err := snap.Persist(sink); err != nil {
   479  		t.Fatalf("err: %v", err)
   480  	}
   481  
   482  	// Try to restore on a new FSM
   483  	fsm2, err := New(nil, os.Stderr)
   484  	if err != nil {
   485  		t.Fatalf("err: %v", err)
   486  	}
   487  
   488  	// Do a restore
   489  	if err := fsm2.Restore(sink); err != nil {
   490  		t.Fatalf("err: %v", err)
   491  	}
   492  
   493  	// Make sure there's no entry in the CA config table.
   494  	state := fsm2.State()
   495  	idx, config, err := state.CAConfig()
   496  	require.NoError(err)
   497  	require.Equal(uint64(0), idx)
   498  	if config != nil {
   499  		t.Fatalf("config should be nil")
   500  	}
   501  }