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 }