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 }