github.com/grahambrereton-form3/tilt@v0.10.18/internal/engine/portforwardcontroller_test.go (about)

     1  package engine
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	v1 "k8s.io/api/core/v1"
    11  
    12  	"github.com/windmilleng/tilt/internal/k8s"
    13  	"github.com/windmilleng/tilt/internal/store"
    14  	"github.com/windmilleng/tilt/internal/testutils/bufsync"
    15  	"github.com/windmilleng/tilt/internal/testutils/tempdir"
    16  	"github.com/windmilleng/tilt/pkg/logger"
    17  	"github.com/windmilleng/tilt/pkg/model"
    18  )
    19  
    20  func TestPortForward(t *testing.T) {
    21  	f := newPLCFixture(t)
    22  	defer f.TearDown()
    23  
    24  	state := f.st.LockMutableStateForTesting()
    25  	m := model.Manifest{
    26  		Name: "fe",
    27  	}
    28  	m = m.WithDeployTarget(model.K8sTarget{
    29  		PortForwards: []model.PortForward{
    30  			{
    31  				LocalPort:     8080,
    32  				ContainerPort: 8081,
    33  			},
    34  		},
    35  	})
    36  	state.UpsertManifestTarget(store.NewManifestTarget(m))
    37  	f.st.UnlockMutableState()
    38  
    39  	f.onChange()
    40  	assert.Equal(t, 0, len(f.plc.activeForwards))
    41  
    42  	state = f.st.LockMutableStateForTesting()
    43  	state.ManifestTargets["fe"].State.RuntimeState = store.NewK8sRuntimeState(store.Pod{PodID: "pod-id", Phase: v1.PodRunning})
    44  	f.st.UnlockMutableState()
    45  
    46  	f.onChange()
    47  	assert.Equal(t, 1, len(f.plc.activeForwards))
    48  	assert.Equal(t, "pod-id", f.kCli.LastForwardPortPodID.String())
    49  	firstPodForwardCtx := f.kCli.LastForwardContext
    50  
    51  	state = f.st.LockMutableStateForTesting()
    52  	state.ManifestTargets["fe"].State.RuntimeState = store.NewK8sRuntimeState(store.Pod{PodID: "pod-id2", Phase: v1.PodRunning})
    53  	f.st.UnlockMutableState()
    54  
    55  	f.onChange()
    56  	assert.Equal(t, 1, len(f.plc.activeForwards))
    57  	assert.Equal(t, "pod-id2", f.kCli.LastForwardPortPodID.String())
    58  
    59  	state = f.st.LockMutableStateForTesting()
    60  	state.ManifestTargets["fe"].State.RuntimeState = store.NewK8sRuntimeState(store.Pod{PodID: "pod-id2", Phase: v1.PodPending})
    61  	f.st.UnlockMutableState()
    62  
    63  	f.onChange()
    64  	assert.Equal(t, 0, len(f.plc.activeForwards))
    65  
    66  	assert.Equal(t, context.Canceled, firstPodForwardCtx.Err(),
    67  		"Expected first port-forward to be canceled")
    68  }
    69  
    70  func TestPortForwardAutoDiscovery(t *testing.T) {
    71  	f := newPLCFixture(t)
    72  	defer f.TearDown()
    73  
    74  	state := f.st.LockMutableStateForTesting()
    75  	m := model.Manifest{
    76  		Name: "fe",
    77  	}
    78  	m = m.WithDeployTarget(model.K8sTarget{
    79  		PortForwards: []model.PortForward{
    80  			{
    81  				LocalPort: 8080,
    82  			},
    83  		},
    84  	})
    85  	state.UpsertManifestTarget(store.NewManifestTarget(m))
    86  	state.ManifestTargets["fe"].State.RuntimeState = store.NewK8sRuntimeState(store.Pod{PodID: "pod-id", Phase: v1.PodRunning})
    87  	f.st.UnlockMutableState()
    88  
    89  	f.onChange()
    90  	assert.Equal(t, 0, len(f.plc.activeForwards))
    91  	state = f.st.LockMutableStateForTesting()
    92  	state.ManifestTargets["fe"].State.K8sRuntimeState().Pods["pod-id"].Containers = []store.Container{
    93  		store.Container{Ports: []int32{8000}},
    94  	}
    95  	f.st.UnlockMutableState()
    96  
    97  	f.onChange()
    98  	assert.Equal(t, 1, len(f.plc.activeForwards))
    99  	assert.Equal(t, 8000, f.kCli.LastForwardPortRemotePort)
   100  }
   101  
   102  func TestPortForwardAutoDiscovery2(t *testing.T) {
   103  	f := newPLCFixture(t)
   104  	defer f.TearDown()
   105  
   106  	state := f.st.LockMutableStateForTesting()
   107  	m := model.Manifest{
   108  		Name: "fe",
   109  	}
   110  	m = m.WithDeployTarget(model.K8sTarget{
   111  		PortForwards: []model.PortForward{
   112  			{
   113  				LocalPort: 8080,
   114  			},
   115  		},
   116  	})
   117  	state.UpsertManifestTarget(store.NewManifestTarget(m))
   118  	state.ManifestTargets["fe"].State.RuntimeState = store.NewK8sRuntimeState(store.Pod{
   119  		PodID: "pod-id",
   120  		Phase: v1.PodRunning,
   121  		Containers: []store.Container{
   122  			store.Container{Ports: []int32{8000, 8080}},
   123  		},
   124  	})
   125  	f.st.UnlockMutableState()
   126  
   127  	f.onChange()
   128  	assert.Equal(t, 1, len(f.plc.activeForwards))
   129  	assert.Equal(t, 8080, f.kCli.LastForwardPortRemotePort)
   130  }
   131  
   132  func TestPortForwardChangePort(t *testing.T) {
   133  	f := newPLCFixture(t)
   134  	defer f.TearDown()
   135  
   136  	state := f.st.LockMutableStateForTesting()
   137  	m := model.Manifest{Name: "fe"}.WithDeployTarget(model.K8sTarget{
   138  		PortForwards: []model.PortForward{
   139  			{
   140  				LocalPort:     8080,
   141  				ContainerPort: 8081,
   142  			},
   143  		},
   144  	})
   145  	state.UpsertManifestTarget(store.NewManifestTarget(m))
   146  	state.ManifestTargets["fe"].State.RuntimeState = store.NewK8sRuntimeState(store.Pod{PodID: "pod-id", Phase: v1.PodRunning})
   147  	f.st.UnlockMutableState()
   148  
   149  	f.onChange()
   150  	assert.Equal(t, 1, len(f.plc.activeForwards))
   151  	assert.Equal(t, 8081, f.kCli.LastForwardPortRemotePort)
   152  
   153  	state = f.st.LockMutableStateForTesting()
   154  	kTarget := state.ManifestTargets["fe"].Manifest.K8sTarget()
   155  	kTarget.PortForwards[0].ContainerPort = 8082
   156  	f.st.UnlockMutableState()
   157  
   158  	f.onChange()
   159  	assert.Equal(t, 1, len(f.plc.activeForwards))
   160  	assert.Equal(t, 8082, f.kCli.LastForwardPortRemotePort)
   161  }
   162  
   163  func TestPortForwardRestart(t *testing.T) {
   164  	f := newPLCFixture(t)
   165  	defer f.TearDown()
   166  
   167  	state := f.st.LockMutableStateForTesting()
   168  	m := model.Manifest{Name: "fe"}.WithDeployTarget(model.K8sTarget{
   169  		PortForwards: []model.PortForward{
   170  			{
   171  				LocalPort:     8080,
   172  				ContainerPort: 8081,
   173  			},
   174  		},
   175  	})
   176  	state.UpsertManifestTarget(store.NewManifestTarget(m))
   177  	state.ManifestTargets["fe"].State.RuntimeState = store.NewK8sRuntimeState(store.Pod{PodID: "pod-id", Phase: v1.PodRunning})
   178  	f.st.UnlockMutableState()
   179  
   180  	f.onChange()
   181  	assert.Equal(t, 1, len(f.plc.activeForwards))
   182  	assert.Equal(t, 1, f.kCli.CreatePortForwardCallCount)
   183  
   184  	err := fmt.Errorf("unique-error")
   185  	f.kCli.LastForwarder.Done <- err
   186  
   187  	assert.Contains(t, "unique-error", f.out.String())
   188  	time.Sleep(100 * time.Millisecond)
   189  
   190  	assert.Equal(t, 1, len(f.plc.activeForwards))
   191  	assert.Equal(t, 2, f.kCli.CreatePortForwardCallCount)
   192  }
   193  
   194  type portForwardTestCase struct {
   195  	spec           []model.PortForward
   196  	containerPorts []int32
   197  	expected       []model.PortForward
   198  }
   199  
   200  func TestPopulatePortForward(t *testing.T) {
   201  	cases := []portForwardTestCase{
   202  		{
   203  			spec:           []model.PortForward{{LocalPort: 8080}},
   204  			containerPorts: []int32{8080},
   205  			expected:       []model.PortForward{{ContainerPort: 8080, LocalPort: 8080}},
   206  		},
   207  		{
   208  			spec:           []model.PortForward{{LocalPort: 8080}},
   209  			containerPorts: []int32{8000, 8080, 8001},
   210  			expected:       []model.PortForward{{ContainerPort: 8080, LocalPort: 8080}},
   211  		},
   212  		{
   213  			spec:           []model.PortForward{{LocalPort: 8080}, {LocalPort: 8000}},
   214  			containerPorts: []int32{8000, 8080, 8001},
   215  			expected: []model.PortForward{
   216  				{ContainerPort: 8080, LocalPort: 8080},
   217  				{ContainerPort: 8000, LocalPort: 8000},
   218  			},
   219  		},
   220  		{
   221  			spec:           []model.PortForward{{ContainerPort: 8000, LocalPort: 8080}},
   222  			containerPorts: []int32{8000, 8080, 8001},
   223  			expected:       []model.PortForward{{ContainerPort: 8000, LocalPort: 8080}},
   224  		},
   225  	}
   226  
   227  	for i, c := range cases {
   228  		t.Run(fmt.Sprintf("Case%d", i), func(t *testing.T) {
   229  			m := model.Manifest{Name: "fe"}.WithDeployTarget(model.K8sTarget{
   230  				PortForwards: c.spec,
   231  			})
   232  			pod := store.Pod{
   233  				Containers: []store.Container{
   234  					store.Container{Ports: c.containerPorts},
   235  				},
   236  			}
   237  
   238  			actual := populatePortForwards(m, pod)
   239  			assert.Equal(t, c.expected, actual)
   240  		})
   241  	}
   242  }
   243  
   244  type plcFixture struct {
   245  	*tempdir.TempDirFixture
   246  	ctx    context.Context
   247  	cancel func()
   248  	kCli   *k8s.FakeK8sClient
   249  	st     *store.Store
   250  	plc    *PortForwardController
   251  	out    *bufsync.ThreadSafeBuffer
   252  }
   253  
   254  func newPLCFixture(t *testing.T) *plcFixture {
   255  	f := tempdir.NewTempDirFixture(t)
   256  	st, _ := store.NewStoreForTesting()
   257  	kCli := k8s.NewFakeK8sClient()
   258  	plc := NewPortForwardController(kCli)
   259  
   260  	out := bufsync.NewThreadSafeBuffer()
   261  	l := logger.NewLogger(logger.DebugLvl, out)
   262  	ctx, cancel := context.WithCancel(context.Background())
   263  	ctx = logger.WithLogger(ctx, l)
   264  	return &plcFixture{
   265  		TempDirFixture: f,
   266  		ctx:            ctx,
   267  		cancel:         cancel,
   268  		st:             st,
   269  		kCli:           kCli,
   270  		plc:            plc,
   271  		out:            out,
   272  	}
   273  }
   274  
   275  func (f *plcFixture) onChange() {
   276  	f.plc.OnChange(f.ctx, f.st)
   277  	time.Sleep(10 * time.Millisecond)
   278  }
   279  
   280  func (f *plcFixture) TearDown() {
   281  	f.kCli.TearDown()
   282  	f.TempDirFixture.TearDown()
   283  	f.cancel()
   284  }