github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/controllers/apis/cluster/client_test.go (about)

     1  package cluster
     2  
     3  import (
     4  	"errors"
     5  	"math/rand"
     6  	"strconv"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/require"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  
    13  	"github.com/tilt-dev/tilt/internal/controllers/fake"
    14  	"github.com/tilt-dev/tilt/internal/k8s"
    15  	"github.com/tilt-dev/tilt/internal/timecmp"
    16  	"github.com/tilt-dev/tilt/pkg/apis"
    17  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    18  )
    19  
    20  func TestClientManager_GetK8sClient_Success(t *testing.T) {
    21  	f := newCmFixture(t)
    22  	c := f.setupSuccessCluster()
    23  
    24  	obj := newFakeObj()
    25  	clusterRef := clusterRef{clusterKey: apis.Key(c), objKey: apis.Key(obj)}
    26  	// ensure we can retrieve the client multiple times
    27  	for i := 0; i < 5; i++ {
    28  		cli, err := f.cm.GetK8sClient(obj, c)
    29  		require.NoError(t, err)
    30  		require.NotNil(t, cli)
    31  
    32  		// manager should be tracking revision and it shouldn't change
    33  		timecmp.AssertTimeEqual(t, c.Status.ConnectedAt, f.cm.revisions[clusterRef])
    34  	}
    35  }
    36  
    37  func TestClientManager_GetK8sClient_NotFound(t *testing.T) {
    38  	f := newCmFixture(t)
    39  	// create a stub cluster obj that's not known by the ClientProvider
    40  	c := newCluster()
    41  
    42  	obj := newFakeObj()
    43  	cli, err := f.cm.GetK8sClient(obj, c)
    44  	require.EqualError(t, err, "cluster client does not exist")
    45  	require.Nil(t, cli)
    46  
    47  	// no revision should have been stored
    48  	require.Empty(t, f.cm.revisions)
    49  }
    50  
    51  func TestClientManager_GetK8sClient_Stale(t *testing.T) {
    52  	f := newCmFixture(t)
    53  	c := f.setupSuccessCluster()
    54  
    55  	obj := newFakeObj()
    56  	old := apis.NewMicroTime(c.Status.ConnectedAt.Add(-time.Second))
    57  	c.Status.ConnectedAt = &old
    58  	cli, err := f.cm.GetK8sClient(obj, c)
    59  	require.EqualError(t, err, "cluster revision is stale")
    60  	require.Nil(t, cli)
    61  
    62  	// no revision should have been stored
    63  	require.Empty(t, f.cm.revisions)
    64  }
    65  
    66  func TestClientManager_GetK8sClient_Error(t *testing.T) {
    67  	f := newCmFixture(t)
    68  	c := f.setupErrorCluster("oh no")
    69  
    70  	obj := newFakeObj()
    71  	cli, err := f.cm.GetK8sClient(obj, c)
    72  	require.EqualError(t, err, "oh no")
    73  	require.Nil(t, cli)
    74  
    75  	// no revision should have been stored
    76  	require.Empty(t, f.cm.revisions)
    77  }
    78  
    79  func TestClientManager_GetK8sClient_Refresh(t *testing.T) {
    80  	f := newCmFixture(t)
    81  	origCluster := f.setupSuccessCluster()
    82  
    83  	objA := newFakeObj()
    84  	objB := newFakeObj()
    85  
    86  	// fetch once for each obj so that it's tracked
    87  	for _, obj := range []apis.KeyableObject{objA, objB} {
    88  		cli, err := f.cm.GetK8sClient(obj, origCluster)
    89  		require.NoError(t, err)
    90  		require.NotNil(t, cli)
    91  	}
    92  
    93  	// update the client
    94  	updatedCluster := origCluster.DeepCopy()
    95  	newRevision := f.cp.SetK8sClient(apis.Key(updatedCluster), k8s.NewFakeK8sClient(t))
    96  	updatedCluster.Status.ConnectedAt = newRevision.DeepCopy()
    97  
    98  	for _, obj := range []apis.KeyableObject{objA, objB} {
    99  		clusterRef := clusterRef{clusterKey: apis.Key(origCluster), objKey: apis.Key(obj)}
   100  
   101  		cli, err := f.cm.GetK8sClient(obj, origCluster)
   102  		require.EqualError(t, err, "cluster revision is stale")
   103  		require.Nil(t, cli)
   104  
   105  		// if we pass in the stale cluster object, no refresh should happen
   106  		require.False(t, f.cm.Refresh(obj, origCluster), "Refresh should return false with original cluster")
   107  
   108  		// revision is still the old one
   109  		timecmp.AssertTimeEqual(t, origCluster.Status.ConnectedAt, f.cm.revisions[clusterRef])
   110  
   111  		// once we pass in the updated cluster object, a refresh SHOULD happen
   112  		// note that this happens for BOTH objA and objB because they might have
   113  		// independently managed state for the cluster!
   114  		require.True(t, f.cm.Refresh(obj, updatedCluster), "Refresh should have been triggered")
   115  
   116  		// since the refresh occurred, the revision should have been wiped
   117  		require.Zero(t, f.cm.revisions[clusterRef], "Revision should have been forgotten")
   118  
   119  		// we can get the new client now!
   120  		cli, err = f.cm.GetK8sClient(obj, updatedCluster)
   121  		require.NoError(t, err)
   122  		require.NotNil(t, cli)
   123  
   124  		// revision is now the new one
   125  		timecmp.AssertTimeEqual(t, updatedCluster.Status.ConnectedAt, f.cm.revisions[clusterRef])
   126  	}
   127  }
   128  
   129  type cmFixture struct {
   130  	t  testing.TB
   131  	cp *FakeClientProvider
   132  	cm *ClientManager
   133  }
   134  
   135  func newCmFixture(t testing.TB) *cmFixture {
   136  	cp := NewFakeClientProvider(t, fake.NewFakeTiltClient())
   137  	return &cmFixture{
   138  		t:  t,
   139  		cp: cp,
   140  		cm: NewClientManager(cp),
   141  	}
   142  }
   143  
   144  type fakeObj struct {
   145  	name      string
   146  	namespace string
   147  }
   148  
   149  var _ apis.KeyableObject = fakeObj{}
   150  
   151  func (f fakeObj) GetName() string {
   152  	return f.name
   153  }
   154  
   155  func (f fakeObj) GetNamespace() string {
   156  	return f.namespace
   157  }
   158  
   159  func newFakeObj() fakeObj {
   160  	return fakeObj{
   161  		namespace: strconv.Itoa(rand.Int()),
   162  		name:      strconv.Itoa(rand.Int()),
   163  	}
   164  }
   165  
   166  func (f *cmFixture) setupSuccessCluster() *v1alpha1.Cluster {
   167  	c := newCluster()
   168  	c.Status.Arch = "amd64"
   169  
   170  	// get the timestamp from the fake client provider and set it
   171  	ts := f.cp.SetK8sClient(apis.Key(c), k8s.NewFakeK8sClient(f.t))
   172  	c.Status.ConnectedAt = ts.DeepCopy()
   173  
   174  	return c
   175  }
   176  
   177  func (f *cmFixture) setupErrorCluster(error string) *v1alpha1.Cluster {
   178  	c := newCluster()
   179  	c.Status.Error = error
   180  
   181  	f.cp.SetClusterError(apis.Key(c), errors.New(error))
   182  	return c
   183  }
   184  
   185  func newCluster() *v1alpha1.Cluster {
   186  	c := &v1alpha1.Cluster{
   187  		ObjectMeta: metav1.ObjectMeta{
   188  			Namespace: strconv.Itoa(rand.Int()),
   189  			Name:      strconv.Itoa(rand.Int()),
   190  		},
   191  	}
   192  	return c
   193  }