github.com/opentofu/opentofu@v1.7.1/internal/backend/remote-state/kubernetes/backend_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package kubernetes
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"math/rand"
    12  	"os"
    13  	"sync"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/opentofu/opentofu/internal/backend"
    18  	"github.com/opentofu/opentofu/internal/encryption"
    19  	"github.com/opentofu/opentofu/internal/states/statemgr"
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  )
    22  
    23  const (
    24  	secretSuffix = "test-state"
    25  )
    26  
    27  var namespace string
    28  
    29  // verify that we are doing ACC tests or the k8s tests specifically
    30  func testACC(t *testing.T) {
    31  	skip := os.Getenv("TF_ACC") == "" && os.Getenv("TF_K8S_TEST") == ""
    32  	if skip {
    33  		t.Log("k8s backend tests require setting TF_ACC or TF_K8S_TEST")
    34  		t.Skip()
    35  	}
    36  
    37  	ns := os.Getenv("KUBE_NAMESPACE")
    38  
    39  	if ns != "" {
    40  		namespace = ns
    41  	} else {
    42  		namespace = "default"
    43  	}
    44  
    45  	cleanupK8sResources(t)
    46  }
    47  
    48  func TestBackend_impl(t *testing.T) {
    49  	var _ backend.Backend = new(Backend)
    50  }
    51  
    52  func TestBackend(t *testing.T) {
    53  	testACC(t)
    54  	defer cleanupK8sResources(t)
    55  
    56  	b1 := backend.TestBackendConfig(t, New(encryption.StateEncryptionDisabled()), backend.TestWrapConfig(map[string]interface{}{
    57  		"secret_suffix": secretSuffix,
    58  	}))
    59  
    60  	// Test
    61  	backend.TestBackendStates(t, b1)
    62  }
    63  
    64  func TestBackendLocks(t *testing.T) {
    65  	testACC(t)
    66  	defer cleanupK8sResources(t)
    67  
    68  	// Get the backend. We need two to test locking.
    69  	b1 := backend.TestBackendConfig(t, New(encryption.StateEncryptionDisabled()), backend.TestWrapConfig(map[string]interface{}{
    70  		"secret_suffix": secretSuffix,
    71  	}))
    72  
    73  	b2 := backend.TestBackendConfig(t, New(encryption.StateEncryptionDisabled()), backend.TestWrapConfig(map[string]interface{}{
    74  		"secret_suffix": secretSuffix,
    75  	}))
    76  
    77  	// Test
    78  	backend.TestBackendStateLocks(t, b1, b2)
    79  	backend.TestBackendStateForceUnlock(t, b1, b2)
    80  }
    81  
    82  func TestBackendLocksSoak(t *testing.T) {
    83  	testACC(t)
    84  	defer cleanupK8sResources(t)
    85  
    86  	clientCount := 100
    87  	lockAttempts := 100
    88  
    89  	lockers := []statemgr.Locker{}
    90  	for i := 0; i < clientCount; i++ {
    91  		b := backend.TestBackendConfig(t, New(encryption.StateEncryptionDisabled()), backend.TestWrapConfig(map[string]interface{}{
    92  			"secret_suffix": secretSuffix,
    93  		}))
    94  
    95  		s, err := b.StateMgr(backend.DefaultStateName)
    96  		if err != nil {
    97  			t.Fatalf("Error creating state manager: %v", err)
    98  		}
    99  
   100  		lockers = append(lockers, s.(statemgr.Locker))
   101  	}
   102  
   103  	wg := sync.WaitGroup{}
   104  	for i, l := range lockers {
   105  		wg.Add(1)
   106  		go func(locker statemgr.Locker, n int) {
   107  			defer wg.Done()
   108  
   109  			li := statemgr.NewLockInfo()
   110  			li.Operation = "test"
   111  			li.Who = fmt.Sprintf("client-%v", n)
   112  
   113  			for i := 0; i < lockAttempts; i++ {
   114  				id, err := locker.Lock(li)
   115  				if err != nil {
   116  					continue
   117  				}
   118  
   119  				// hold onto the lock for a little bit
   120  				time.Sleep(time.Duration(rand.Intn(10)) * time.Microsecond)
   121  
   122  				err = locker.Unlock(id)
   123  				if err != nil {
   124  					t.Errorf("failed to unlock: %v", err)
   125  				}
   126  			}
   127  		}(l, i)
   128  	}
   129  
   130  	wg.Wait()
   131  }
   132  
   133  func cleanupK8sResources(t *testing.T) {
   134  	ctx := context.Background()
   135  	// Get a backend to use the k8s client
   136  	b1 := backend.TestBackendConfig(t, New(encryption.StateEncryptionDisabled()), backend.TestWrapConfig(map[string]interface{}{
   137  		"secret_suffix": secretSuffix,
   138  	}))
   139  
   140  	b := b1.(*Backend)
   141  
   142  	sClient, err := b.getKubernetesSecretClient()
   143  	if err != nil {
   144  		t.Fatal(err)
   145  	}
   146  
   147  	// Delete secrets
   148  	opts := metav1.ListOptions{LabelSelector: tfstateKey + "=true"}
   149  	secrets, err := sClient.List(ctx, opts)
   150  	if err != nil {
   151  		t.Fatal(err)
   152  	}
   153  
   154  	delProp := metav1.DeletePropagationBackground
   155  	delOps := metav1.DeleteOptions{PropagationPolicy: &delProp}
   156  	var errs []error
   157  
   158  	for _, secret := range secrets.Items {
   159  		labels := secret.GetLabels()
   160  		key, ok := labels[tfstateSecretSuffixKey]
   161  		if !ok {
   162  			continue
   163  		}
   164  
   165  		if key == secretSuffix {
   166  			err = sClient.Delete(ctx, secret.GetName(), delOps)
   167  			if err != nil {
   168  				errs = append(errs, err)
   169  			}
   170  		}
   171  	}
   172  
   173  	leaseClient, err := b.getKubernetesLeaseClient()
   174  	if err != nil {
   175  		t.Fatal(err)
   176  	}
   177  
   178  	// Delete leases
   179  	leases, err := leaseClient.List(ctx, opts)
   180  	if err != nil {
   181  		t.Fatal(err)
   182  	}
   183  
   184  	for _, lease := range leases.Items {
   185  		labels := lease.GetLabels()
   186  		key, ok := labels[tfstateSecretSuffixKey]
   187  		if !ok {
   188  			continue
   189  		}
   190  
   191  		if key == secretSuffix {
   192  			err = leaseClient.Delete(ctx, lease.GetName(), delOps)
   193  			if err != nil {
   194  				errs = append(errs, err)
   195  			}
   196  		}
   197  	}
   198  
   199  	if len(errs) > 0 {
   200  		t.Fatal(errs)
   201  	}
   202  }