github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/backend/remote-state/consul/client_test.go (about)

     1  package consul
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"math/rand"
     9  	"net"
    10  	"reflect"
    11  	"strings"
    12  	"sync"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/hashicorp/terraform/internal/backend"
    17  	"github.com/hashicorp/terraform/internal/states/remote"
    18  	"github.com/hashicorp/terraform/internal/states/statemgr"
    19  )
    20  
    21  func TestRemoteClient_impl(t *testing.T) {
    22  	var _ remote.Client = new(RemoteClient)
    23  	var _ remote.ClientLocker = new(RemoteClient)
    24  }
    25  
    26  func TestRemoteClient(t *testing.T) {
    27  	srv := newConsulTestServer(t)
    28  
    29  	testCases := []string{
    30  		fmt.Sprintf("tf-unit/%s", time.Now().String()),
    31  		fmt.Sprintf("tf-unit/%s/", time.Now().String()),
    32  	}
    33  
    34  	for _, path := range testCases {
    35  		t.Run(path, func(*testing.T) {
    36  			// Get the backend
    37  			b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
    38  				"address": srv.HTTPAddr,
    39  				"path":    path,
    40  			}))
    41  
    42  			// Grab the client
    43  			state, err := b.StateMgr(backend.DefaultStateName)
    44  			if err != nil {
    45  				t.Fatalf("err: %s", err)
    46  			}
    47  
    48  			// Test
    49  			remote.TestClient(t, state.(*remote.State).Client)
    50  		})
    51  	}
    52  }
    53  
    54  // test the gzip functionality of the client
    55  func TestRemoteClient_gzipUpgrade(t *testing.T) {
    56  	srv := newConsulTestServer(t)
    57  
    58  	statePath := fmt.Sprintf("tf-unit/%s", time.Now().String())
    59  
    60  	// Get the backend
    61  	b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
    62  		"address": srv.HTTPAddr,
    63  		"path":    statePath,
    64  	}))
    65  
    66  	// Grab the client
    67  	state, err := b.StateMgr(backend.DefaultStateName)
    68  	if err != nil {
    69  		t.Fatalf("err: %s", err)
    70  	}
    71  
    72  	// Test
    73  	remote.TestClient(t, state.(*remote.State).Client)
    74  
    75  	// create a new backend with gzip
    76  	b = backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
    77  		"address": srv.HTTPAddr,
    78  		"path":    statePath,
    79  		"gzip":    true,
    80  	}))
    81  
    82  	// Grab the client
    83  	state, err = b.StateMgr(backend.DefaultStateName)
    84  	if err != nil {
    85  		t.Fatalf("err: %s", err)
    86  	}
    87  
    88  	// Test
    89  	remote.TestClient(t, state.(*remote.State).Client)
    90  }
    91  
    92  // TestConsul_largeState tries to write a large payload using the Consul state
    93  // manager, as there is a limit to the size of the values in the KV store it
    94  // will need to be split up before being saved and put back together when read.
    95  func TestConsul_largeState(t *testing.T) {
    96  	srv := newConsulTestServer(t)
    97  
    98  	path := "tf-unit/test-large-state"
    99  
   100  	b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   101  		"address": srv.HTTPAddr,
   102  		"path":    path,
   103  	}))
   104  
   105  	s, err := b.StateMgr(backend.DefaultStateName)
   106  	if err != nil {
   107  		t.Fatal(err)
   108  	}
   109  
   110  	c := s.(*remote.State).Client.(*RemoteClient)
   111  	c.Path = path
   112  
   113  	// testPaths fails the test if the keys found at the prefix don't match
   114  	// what is expected
   115  	testPaths := func(t *testing.T, expected []string) {
   116  		kv := c.Client.KV()
   117  		pairs, _, err := kv.List(c.Path, nil)
   118  		if err != nil {
   119  			t.Fatal(err)
   120  		}
   121  		res := make([]string, 0)
   122  		for _, p := range pairs {
   123  			res = append(res, p.Key)
   124  		}
   125  		if !reflect.DeepEqual(res, expected) {
   126  			t.Fatalf("Wrong keys: %#v", res)
   127  		}
   128  	}
   129  
   130  	testPayload := func(t *testing.T, data map[string]string, keys []string) {
   131  		payload, err := json.Marshal(data)
   132  		if err != nil {
   133  			t.Fatal(err)
   134  		}
   135  		err = c.Put(payload)
   136  		if err != nil {
   137  			t.Fatal("could not put payload", err)
   138  		}
   139  
   140  		remote, err := c.Get()
   141  		if err != nil {
   142  			t.Fatal(err)
   143  		}
   144  
   145  		if !bytes.Equal(payload, remote.Data) {
   146  			t.Fatal("the data do not match")
   147  		}
   148  
   149  		testPaths(t, keys)
   150  	}
   151  
   152  	// The default limit for the size of the value in Consul is 524288 bytes
   153  	testPayload(
   154  		t,
   155  		map[string]string{
   156  			"foo": strings.Repeat("a", 524288+2),
   157  		},
   158  		[]string{
   159  			"tf-unit/test-large-state",
   160  			"tf-unit/test-large-state/tfstate.2cb96f52c9fff8e0b56cb786ec4d2bed/0",
   161  			"tf-unit/test-large-state/tfstate.2cb96f52c9fff8e0b56cb786ec4d2bed/1",
   162  		},
   163  	)
   164  
   165  	// This payload is just short enough to be stored but will be bigger when
   166  	// going through the Transaction API as it will be base64 encoded
   167  	testPayload(
   168  		t,
   169  		map[string]string{
   170  			"foo": strings.Repeat("a", 524288-10),
   171  		},
   172  		[]string{
   173  			"tf-unit/test-large-state",
   174  			"tf-unit/test-large-state/tfstate.4f407ace136a86521fd0d366972fe5c7/0",
   175  		},
   176  	)
   177  
   178  	// We try to replace the payload with a small one, the old chunks should be removed
   179  	testPayload(
   180  		t,
   181  		map[string]string{"var": "a"},
   182  		[]string{"tf-unit/test-large-state"},
   183  	)
   184  
   185  	// Test with gzip and chunks
   186  	b = backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   187  		"address": srv.HTTPAddr,
   188  		"path":    path,
   189  		"gzip":    true,
   190  	}))
   191  
   192  	s, err = b.StateMgr(backend.DefaultStateName)
   193  	if err != nil {
   194  		t.Fatal(err)
   195  	}
   196  
   197  	c = s.(*remote.State).Client.(*RemoteClient)
   198  	c.Path = path
   199  
   200  	// We need a long random string so it results in multiple chunks even after
   201  	// being gziped
   202  
   203  	// We use a fixed seed so the test can be reproductible
   204  	rand.Seed(1234)
   205  	RandStringRunes := func(n int) string {
   206  		var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
   207  		b := make([]rune, n)
   208  		for i := range b {
   209  			b[i] = letterRunes[rand.Intn(len(letterRunes))]
   210  		}
   211  		return string(b)
   212  	}
   213  
   214  	testPayload(
   215  		t,
   216  		map[string]string{
   217  			"bar": RandStringRunes(5 * (524288 + 2)),
   218  		},
   219  		[]string{
   220  			"tf-unit/test-large-state",
   221  			"tf-unit/test-large-state/tfstate.58e8160335864b520b1cc7f2222a4019/0",
   222  			"tf-unit/test-large-state/tfstate.58e8160335864b520b1cc7f2222a4019/1",
   223  			"tf-unit/test-large-state/tfstate.58e8160335864b520b1cc7f2222a4019/2",
   224  			"tf-unit/test-large-state/tfstate.58e8160335864b520b1cc7f2222a4019/3",
   225  		},
   226  	)
   227  
   228  	// Deleting the state should remove all chunks
   229  	err = c.Delete()
   230  	if err != nil {
   231  		t.Fatal(err)
   232  	}
   233  	testPaths(t, []string{})
   234  }
   235  
   236  func TestConsul_stateLock(t *testing.T) {
   237  	srv := newConsulTestServer(t)
   238  
   239  	testCases := []string{
   240  		fmt.Sprintf("tf-unit/%s", time.Now().String()),
   241  		fmt.Sprintf("tf-unit/%s/", time.Now().String()),
   242  	}
   243  
   244  	for _, path := range testCases {
   245  		t.Run(path, func(*testing.T) {
   246  			// create 2 instances to get 2 remote.Clients
   247  			sA, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   248  				"address": srv.HTTPAddr,
   249  				"path":    path,
   250  			})).StateMgr(backend.DefaultStateName)
   251  			if err != nil {
   252  				t.Fatal(err)
   253  			}
   254  
   255  			sB, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   256  				"address": srv.HTTPAddr,
   257  				"path":    path,
   258  			})).StateMgr(backend.DefaultStateName)
   259  			if err != nil {
   260  				t.Fatal(err)
   261  			}
   262  
   263  			remote.TestRemoteLocks(t, sA.(*remote.State).Client, sB.(*remote.State).Client)
   264  		})
   265  	}
   266  }
   267  
   268  func TestConsul_destroyLock(t *testing.T) {
   269  	srv := newConsulTestServer(t)
   270  
   271  	testCases := []string{
   272  		fmt.Sprintf("tf-unit/%s", time.Now().String()),
   273  		fmt.Sprintf("tf-unit/%s/", time.Now().String()),
   274  	}
   275  
   276  	testLock := func(client *RemoteClient, lockPath string) {
   277  		// get the lock val
   278  		pair, _, err := client.Client.KV().Get(lockPath, nil)
   279  		if err != nil {
   280  			t.Fatal(err)
   281  		}
   282  		if pair != nil {
   283  			t.Fatalf("lock key not cleaned up at: %s", pair.Key)
   284  		}
   285  	}
   286  
   287  	for _, path := range testCases {
   288  		t.Run(path, func(*testing.T) {
   289  			// Get the backend
   290  			b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   291  				"address": srv.HTTPAddr,
   292  				"path":    path,
   293  			}))
   294  
   295  			// Grab the client
   296  			s, err := b.StateMgr(backend.DefaultStateName)
   297  			if err != nil {
   298  				t.Fatalf("err: %s", err)
   299  			}
   300  
   301  			clientA := s.(*remote.State).Client.(*RemoteClient)
   302  
   303  			info := statemgr.NewLockInfo()
   304  			id, err := clientA.Lock(info)
   305  			if err != nil {
   306  				t.Fatal(err)
   307  			}
   308  
   309  			lockPath := clientA.Path + lockSuffix
   310  
   311  			if err := clientA.Unlock(id); err != nil {
   312  				t.Fatal(err)
   313  			}
   314  
   315  			testLock(clientA, lockPath)
   316  
   317  			// The release the lock from a second client to test the
   318  			// `terraform force-unlock <lock_id>` functionnality
   319  			s, err = b.StateMgr(backend.DefaultStateName)
   320  			if err != nil {
   321  				t.Fatalf("err: %s", err)
   322  			}
   323  
   324  			clientB := s.(*remote.State).Client.(*RemoteClient)
   325  
   326  			info = statemgr.NewLockInfo()
   327  			id, err = clientA.Lock(info)
   328  			if err != nil {
   329  				t.Fatal(err)
   330  			}
   331  
   332  			if err := clientB.Unlock(id); err != nil {
   333  				t.Fatal(err)
   334  			}
   335  
   336  			testLock(clientA, lockPath)
   337  
   338  			err = clientA.Unlock(id)
   339  
   340  			if err == nil {
   341  				t.Fatal("consul lock should have been lost")
   342  			}
   343  			if err.Error() != "consul lock was lost" {
   344  				t.Fatal("got wrong error", err)
   345  			}
   346  		})
   347  	}
   348  }
   349  
   350  func TestConsul_lostLock(t *testing.T) {
   351  	srv := newConsulTestServer(t)
   352  
   353  	path := fmt.Sprintf("tf-unit/%s", time.Now().String())
   354  
   355  	// create 2 instances to get 2 remote.Clients
   356  	sA, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   357  		"address": srv.HTTPAddr,
   358  		"path":    path,
   359  	})).StateMgr(backend.DefaultStateName)
   360  	if err != nil {
   361  		t.Fatal(err)
   362  	}
   363  
   364  	sB, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   365  		"address": srv.HTTPAddr,
   366  		"path":    path + "-not-used",
   367  	})).StateMgr(backend.DefaultStateName)
   368  	if err != nil {
   369  		t.Fatal(err)
   370  	}
   371  
   372  	info := statemgr.NewLockInfo()
   373  	info.Operation = "test-lost-lock"
   374  	id, err := sA.Lock(info)
   375  	if err != nil {
   376  		t.Fatal(err)
   377  	}
   378  
   379  	reLocked := make(chan struct{})
   380  	testLockHook = func() {
   381  		close(reLocked)
   382  		testLockHook = nil
   383  	}
   384  
   385  	// now we use the second client to break the lock
   386  	kv := sB.(*remote.State).Client.(*RemoteClient).Client.KV()
   387  	_, err = kv.Delete(path+lockSuffix, nil)
   388  	if err != nil {
   389  		t.Fatal(err)
   390  	}
   391  
   392  	<-reLocked
   393  
   394  	if err := sA.Unlock(id); err != nil {
   395  		t.Fatal(err)
   396  	}
   397  }
   398  
   399  func TestConsul_lostLockConnection(t *testing.T) {
   400  	srv := newConsulTestServer(t)
   401  
   402  	// create an "unreliable" network by closing all the consul client's
   403  	// network connections
   404  	conns := &unreliableConns{}
   405  	origDialFn := dialContext
   406  	defer func() {
   407  		dialContext = origDialFn
   408  	}()
   409  	dialContext = conns.DialContext
   410  
   411  	path := fmt.Sprintf("tf-unit/%s", time.Now().String())
   412  
   413  	b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   414  		"address": srv.HTTPAddr,
   415  		"path":    path,
   416  	}))
   417  
   418  	s, err := b.StateMgr(backend.DefaultStateName)
   419  	if err != nil {
   420  		t.Fatal(err)
   421  	}
   422  
   423  	info := statemgr.NewLockInfo()
   424  	info.Operation = "test-lost-lock-connection"
   425  	id, err := s.Lock(info)
   426  	if err != nil {
   427  		t.Fatal(err)
   428  	}
   429  
   430  	// kill the connection a few times
   431  	for i := 0; i < 3; i++ {
   432  		dialed := conns.dialedDone()
   433  		// kill any open connections
   434  		conns.Kill()
   435  		// wait for a new connection to be dialed, and kill it again
   436  		<-dialed
   437  	}
   438  
   439  	if err := s.Unlock(id); err != nil {
   440  		t.Fatal("unlock error:", err)
   441  	}
   442  }
   443  
   444  type unreliableConns struct {
   445  	sync.Mutex
   446  	conns        []net.Conn
   447  	dialCallback func()
   448  }
   449  
   450  func (u *unreliableConns) DialContext(ctx context.Context, netw, addr string) (net.Conn, error) {
   451  	u.Lock()
   452  	defer u.Unlock()
   453  
   454  	dialer := &net.Dialer{}
   455  	conn, err := dialer.DialContext(ctx, netw, addr)
   456  	if err != nil {
   457  		return nil, err
   458  	}
   459  
   460  	u.conns = append(u.conns, conn)
   461  
   462  	if u.dialCallback != nil {
   463  		u.dialCallback()
   464  	}
   465  
   466  	return conn, nil
   467  }
   468  
   469  func (u *unreliableConns) dialedDone() chan struct{} {
   470  	u.Lock()
   471  	defer u.Unlock()
   472  	dialed := make(chan struct{})
   473  	u.dialCallback = func() {
   474  		defer close(dialed)
   475  		u.dialCallback = nil
   476  	}
   477  
   478  	return dialed
   479  }
   480  
   481  // Kill these with a deadline, just to make sure we don't end up with any EOFs
   482  // that get ignored.
   483  func (u *unreliableConns) Kill() {
   484  	u.Lock()
   485  	defer u.Unlock()
   486  
   487  	for _, conn := range u.conns {
   488  		conn.(*net.TCPConn).SetDeadline(time.Now())
   489  	}
   490  	u.conns = nil
   491  }