k8s.io/client-go@v0.22.2/tools/portforward/portforward_test.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package portforward
    18  
    19  import (
    20  	"fmt"
    21  	"net"
    22  	"net/http"
    23  	"os"
    24  	"reflect"
    25  	"sort"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	"k8s.io/apimachinery/pkg/util/httpstream"
    31  )
    32  
    33  type fakeDialer struct {
    34  	dialed             bool
    35  	conn               httpstream.Connection
    36  	err                error
    37  	negotiatedProtocol string
    38  }
    39  
    40  func (d *fakeDialer) Dial(protocols ...string) (httpstream.Connection, string, error) {
    41  	d.dialed = true
    42  	return d.conn, d.negotiatedProtocol, d.err
    43  }
    44  
    45  type fakeConnection struct {
    46  	closed    bool
    47  	closeChan chan bool
    48  }
    49  
    50  func newFakeConnection() httpstream.Connection {
    51  	return &fakeConnection{
    52  		closeChan: make(chan bool),
    53  	}
    54  }
    55  
    56  func (c *fakeConnection) CreateStream(headers http.Header) (httpstream.Stream, error) {
    57  	return nil, nil
    58  }
    59  
    60  func (c *fakeConnection) Close() error {
    61  	if !c.closed {
    62  		c.closed = true
    63  		close(c.closeChan)
    64  	}
    65  	return nil
    66  }
    67  
    68  func (c *fakeConnection) CloseChan() <-chan bool {
    69  	return c.closeChan
    70  }
    71  
    72  func (c *fakeConnection) RemoveStreams(_ ...httpstream.Stream) {
    73  }
    74  
    75  func (c *fakeConnection) SetIdleTimeout(timeout time.Duration) {
    76  	// no-op
    77  }
    78  
    79  func TestParsePortsAndNew(t *testing.T) {
    80  	tests := []struct {
    81  		input                   []string
    82  		addresses               []string
    83  		expectedPorts           []ForwardedPort
    84  		expectedAddresses       []listenAddress
    85  		expectPortParseError    bool
    86  		expectAddressParseError bool
    87  		expectNewError          bool
    88  	}{
    89  		{input: []string{}, expectNewError: true},
    90  		{input: []string{"a"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
    91  		{input: []string{":a"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
    92  		{input: []string{"-1"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
    93  		{input: []string{"65536"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
    94  		{input: []string{"0"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
    95  		{input: []string{"0:0"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
    96  		{input: []string{"a:5000"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
    97  		{input: []string{"5000:a"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
    98  		{input: []string{"5000:5000"}, addresses: []string{"127.0.0.257"}, expectPortParseError: false, expectAddressParseError: true, expectNewError: true},
    99  		{input: []string{"5000:5000"}, addresses: []string{"::g"}, expectPortParseError: false, expectAddressParseError: true, expectNewError: true},
   100  		{input: []string{"5000:5000"}, addresses: []string{"domain.invalid"}, expectPortParseError: false, expectAddressParseError: true, expectNewError: true},
   101  		{
   102  			input:     []string{"5000:5000"},
   103  			addresses: []string{"localhost"},
   104  			expectedPorts: []ForwardedPort{
   105  				{5000, 5000},
   106  			},
   107  			expectedAddresses: []listenAddress{
   108  				{protocol: "tcp4", address: "127.0.0.1", failureMode: "all"},
   109  				{protocol: "tcp6", address: "::1", failureMode: "all"},
   110  			},
   111  		},
   112  		{
   113  			input:     []string{"5000:5000"},
   114  			addresses: []string{"localhost", "127.0.0.1"},
   115  			expectedPorts: []ForwardedPort{
   116  				{5000, 5000},
   117  			},
   118  			expectedAddresses: []listenAddress{
   119  				{protocol: "tcp4", address: "127.0.0.1", failureMode: "any"},
   120  				{protocol: "tcp6", address: "::1", failureMode: "all"},
   121  			},
   122  		},
   123  		{
   124  			input:     []string{"5000:5000"},
   125  			addresses: []string{"localhost", "::1"},
   126  			expectedPorts: []ForwardedPort{
   127  				{5000, 5000},
   128  			},
   129  			expectedAddresses: []listenAddress{
   130  				{protocol: "tcp4", address: "127.0.0.1", failureMode: "all"},
   131  				{protocol: "tcp6", address: "::1", failureMode: "any"},
   132  			},
   133  		},
   134  		{
   135  			input:     []string{"5000:5000"},
   136  			addresses: []string{"localhost", "127.0.0.1", "::1"},
   137  			expectedPorts: []ForwardedPort{
   138  				{5000, 5000},
   139  			},
   140  			expectedAddresses: []listenAddress{
   141  				{protocol: "tcp4", address: "127.0.0.1", failureMode: "any"},
   142  				{protocol: "tcp6", address: "::1", failureMode: "any"},
   143  			},
   144  		},
   145  		{
   146  			input:     []string{"5000:5000"},
   147  			addresses: []string{"localhost", "127.0.0.1", "10.10.10.1"},
   148  			expectedPorts: []ForwardedPort{
   149  				{5000, 5000},
   150  			},
   151  			expectedAddresses: []listenAddress{
   152  				{protocol: "tcp4", address: "127.0.0.1", failureMode: "any"},
   153  				{protocol: "tcp6", address: "::1", failureMode: "all"},
   154  				{protocol: "tcp4", address: "10.10.10.1", failureMode: "any"},
   155  			},
   156  		},
   157  		{
   158  			input:     []string{"5000:5000"},
   159  			addresses: []string{"127.0.0.1", "::1", "localhost"},
   160  			expectedPorts: []ForwardedPort{
   161  				{5000, 5000},
   162  			},
   163  			expectedAddresses: []listenAddress{
   164  				{protocol: "tcp4", address: "127.0.0.1", failureMode: "any"},
   165  				{protocol: "tcp6", address: "::1", failureMode: "any"},
   166  			},
   167  		},
   168  		{
   169  			input:     []string{"5000:5000"},
   170  			addresses: []string{"10.0.0.1", "127.0.0.1"},
   171  			expectedPorts: []ForwardedPort{
   172  				{5000, 5000},
   173  			},
   174  			expectedAddresses: []listenAddress{
   175  				{protocol: "tcp4", address: "10.0.0.1", failureMode: "any"},
   176  				{protocol: "tcp4", address: "127.0.0.1", failureMode: "any"},
   177  			},
   178  		},
   179  		{
   180  			input:     []string{"5000", "5000:5000", "8888:5000", "5000:8888", ":5000", "0:5000"},
   181  			addresses: []string{"127.0.0.1", "::1"},
   182  			expectedPorts: []ForwardedPort{
   183  				{5000, 5000},
   184  				{5000, 5000},
   185  				{8888, 5000},
   186  				{5000, 8888},
   187  				{0, 5000},
   188  				{0, 5000},
   189  			},
   190  			expectedAddresses: []listenAddress{
   191  				{protocol: "tcp4", address: "127.0.0.1", failureMode: "any"},
   192  				{protocol: "tcp6", address: "::1", failureMode: "any"},
   193  			},
   194  		},
   195  	}
   196  
   197  	for i, test := range tests {
   198  		parsedPorts, err := parsePorts(test.input)
   199  		haveError := err != nil
   200  		if e, a := test.expectPortParseError, haveError; e != a {
   201  			t.Fatalf("%d: parsePorts: error expected=%t, got %t: %s", i, e, a, err)
   202  		}
   203  
   204  		// default to localhost
   205  		if len(test.addresses) == 0 && len(test.expectedAddresses) == 0 {
   206  			test.addresses = []string{"localhost"}
   207  			test.expectedAddresses = []listenAddress{{protocol: "tcp4", address: "127.0.0.1"}, {protocol: "tcp6", address: "::1"}}
   208  		}
   209  		// assert address parser
   210  		parsedAddresses, err := parseAddresses(test.addresses)
   211  		haveError = err != nil
   212  		if e, a := test.expectAddressParseError, haveError; e != a {
   213  			t.Fatalf("%d: parseAddresses: error expected=%t, got %t: %s", i, e, a, err)
   214  		}
   215  
   216  		dialer := &fakeDialer{}
   217  		expectedStopChan := make(chan struct{})
   218  		readyChan := make(chan struct{})
   219  
   220  		var pf *PortForwarder
   221  		if len(test.addresses) > 0 {
   222  			pf, err = NewOnAddresses(dialer, test.addresses, test.input, expectedStopChan, readyChan, os.Stdout, os.Stderr)
   223  		} else {
   224  			pf, err = New(dialer, test.input, expectedStopChan, readyChan, os.Stdout, os.Stderr)
   225  		}
   226  		haveError = err != nil
   227  		if e, a := test.expectNewError, haveError; e != a {
   228  			t.Fatalf("%d: New: error expected=%t, got %t: %s", i, e, a, err)
   229  		}
   230  
   231  		if test.expectPortParseError || test.expectAddressParseError || test.expectNewError {
   232  			continue
   233  		}
   234  
   235  		sort.Slice(test.expectedAddresses, func(i, j int) bool { return test.expectedAddresses[i].address < test.expectedAddresses[j].address })
   236  		sort.Slice(parsedAddresses, func(i, j int) bool { return parsedAddresses[i].address < parsedAddresses[j].address })
   237  
   238  		if !reflect.DeepEqual(test.expectedAddresses, parsedAddresses) {
   239  			t.Fatalf("%d: expectedAddresses: %v, got: %v", i, test.expectedAddresses, parsedAddresses)
   240  		}
   241  
   242  		for pi, expectedPort := range test.expectedPorts {
   243  			if e, a := expectedPort.Local, parsedPorts[pi].Local; e != a {
   244  				t.Fatalf("%d: local expected: %d, got: %d", i, e, a)
   245  			}
   246  			if e, a := expectedPort.Remote, parsedPorts[pi].Remote; e != a {
   247  				t.Fatalf("%d: remote expected: %d, got: %d", i, e, a)
   248  			}
   249  		}
   250  
   251  		if dialer.dialed {
   252  			t.Fatalf("%d: expected not dialed", i)
   253  		}
   254  		if _, portErr := pf.GetPorts(); portErr == nil {
   255  			t.Fatalf("%d: GetPorts: error expected but got nil", i)
   256  		}
   257  
   258  		// mock-signal the Ready channel
   259  		close(readyChan)
   260  
   261  		if ports, portErr := pf.GetPorts(); portErr != nil {
   262  			t.Fatalf("%d: GetPorts: unable to retrieve ports: %s", i, portErr)
   263  		} else if !reflect.DeepEqual(test.expectedPorts, ports) {
   264  			t.Fatalf("%d: ports: expected %#v, got %#v", i, test.expectedPorts, ports)
   265  		}
   266  		if e, a := expectedStopChan, pf.stopChan; e != a {
   267  			t.Fatalf("%d: stopChan: expected %#v, got %#v", i, e, a)
   268  		}
   269  		if pf.Ready == nil {
   270  			t.Fatalf("%d: Ready should be non-nil", i)
   271  		}
   272  	}
   273  }
   274  
   275  type GetListenerTestCase struct {
   276  	Hostname                string
   277  	Protocol                string
   278  	ShouldRaiseError        bool
   279  	ExpectedListenerAddress string
   280  }
   281  
   282  func TestGetListener(t *testing.T) {
   283  	var pf PortForwarder
   284  	testCases := []GetListenerTestCase{
   285  		{
   286  			Hostname:                "localhost",
   287  			Protocol:                "tcp4",
   288  			ShouldRaiseError:        false,
   289  			ExpectedListenerAddress: "127.0.0.1",
   290  		},
   291  		{
   292  			Hostname:                "127.0.0.1",
   293  			Protocol:                "tcp4",
   294  			ShouldRaiseError:        false,
   295  			ExpectedListenerAddress: "127.0.0.1",
   296  		},
   297  		{
   298  			Hostname:                "::1",
   299  			Protocol:                "tcp6",
   300  			ShouldRaiseError:        false,
   301  			ExpectedListenerAddress: "::1",
   302  		},
   303  		{
   304  			Hostname:         "::1",
   305  			Protocol:         "tcp4",
   306  			ShouldRaiseError: true,
   307  		},
   308  		{
   309  			Hostname:         "127.0.0.1",
   310  			Protocol:         "tcp6",
   311  			ShouldRaiseError: true,
   312  		},
   313  	}
   314  
   315  	for i, testCase := range testCases {
   316  		forwardedPort := &ForwardedPort{Local: 0, Remote: 12345}
   317  		listener, err := pf.getListener(testCase.Protocol, testCase.Hostname, forwardedPort)
   318  		if err != nil && strings.Contains(err.Error(), "cannot assign requested address") {
   319  			t.Logf("Can't test #%d: %v", i, err)
   320  			continue
   321  		}
   322  		expectedListenerPort := fmt.Sprintf("%d", forwardedPort.Local)
   323  		errorRaised := err != nil
   324  
   325  		if testCase.ShouldRaiseError != errorRaised {
   326  			t.Errorf("Test case #%d failed: Data %v an error has been raised(%t) where it should not (or reciprocally): %v", i, testCase, testCase.ShouldRaiseError, err)
   327  			continue
   328  		}
   329  		if errorRaised {
   330  			continue
   331  		}
   332  
   333  		if listener == nil {
   334  			t.Errorf("Test case #%d did not raise an error but failed in initializing listener", i)
   335  			continue
   336  		}
   337  
   338  		host, port, _ := net.SplitHostPort(listener.Addr().String())
   339  		t.Logf("Asked a %s forward for: %s:0, got listener %s:%s, expected: %s", testCase.Protocol, testCase.Hostname, host, port, expectedListenerPort)
   340  		if host != testCase.ExpectedListenerAddress {
   341  			t.Errorf("Test case #%d failed: Listener does not listen on expected address: asked '%v' got '%v'", i, testCase.ExpectedListenerAddress, host)
   342  		}
   343  		if port != expectedListenerPort {
   344  			t.Errorf("Test case #%d failed: Listener does not listen on expected port: asked %v got %v", i, expectedListenerPort, port)
   345  
   346  		}
   347  		listener.Close()
   348  	}
   349  }
   350  
   351  func TestGetPortsReturnsDynamicallyAssignedLocalPort(t *testing.T) {
   352  	dialer := &fakeDialer{
   353  		conn: newFakeConnection(),
   354  	}
   355  
   356  	stopChan := make(chan struct{})
   357  	readyChan := make(chan struct{})
   358  	errChan := make(chan error)
   359  
   360  	defer func() {
   361  		close(stopChan)
   362  
   363  		forwardErr := <-errChan
   364  		if forwardErr != nil {
   365  			t.Fatalf("ForwardPorts returned error: %s", forwardErr)
   366  		}
   367  	}()
   368  
   369  	pf, err := New(dialer, []string{":5000"}, stopChan, readyChan, os.Stdout, os.Stderr)
   370  
   371  	if err != nil {
   372  		t.Fatalf("error while calling New: %s", err)
   373  	}
   374  
   375  	go func() {
   376  		errChan <- pf.ForwardPorts()
   377  		close(errChan)
   378  	}()
   379  
   380  	<-pf.Ready
   381  
   382  	ports, err := pf.GetPorts()
   383  	if err != nil {
   384  		t.Fatalf("Failed to get ports. error: %v", err)
   385  	}
   386  
   387  	if len(ports) != 1 {
   388  		t.Fatalf("expected 1 port, got %d", len(ports))
   389  	}
   390  
   391  	port := ports[0]
   392  	if port.Local == 0 {
   393  		t.Fatalf("local port is 0, expected != 0")
   394  	}
   395  }