...

Source file src/k8s.io/utils/net/multi_listen_test.go

Documentation: k8s.io/utils/net

     1  /*
     2  Copyright 2024 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 net
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"net"
    24  	"net/http"
    25  	"strconv"
    26  	"sync/atomic"
    27  	"testing"
    28  	"time"
    29  )
    30  
    31  type fakeCon struct {
    32  	remoteAddr net.Addr
    33  }
    34  
    35  func (f *fakeCon) Read(_ []byte) (n int, err error) {
    36  	return 0, nil
    37  }
    38  
    39  func (f *fakeCon) Write(_ []byte) (n int, err error) {
    40  	return 0, nil
    41  }
    42  
    43  func (f *fakeCon) Close() error {
    44  	return nil
    45  }
    46  
    47  func (f *fakeCon) LocalAddr() net.Addr {
    48  	return nil
    49  }
    50  
    51  func (f *fakeCon) RemoteAddr() net.Addr {
    52  	return f.remoteAddr
    53  }
    54  
    55  func (f *fakeCon) SetDeadline(_ time.Time) error {
    56  	return nil
    57  }
    58  
    59  func (f *fakeCon) SetReadDeadline(_ time.Time) error {
    60  	return nil
    61  }
    62  
    63  func (f *fakeCon) SetWriteDeadline(_ time.Time) error {
    64  	return nil
    65  }
    66  
    67  var _ net.Conn = &fakeCon{}
    68  
    69  type fakeListener struct {
    70  	addr         net.Addr
    71  	index        int
    72  	err          error
    73  	closed       atomic.Bool
    74  	connErrPairs []connErrPair
    75  }
    76  
    77  func (f *fakeListener) Accept() (net.Conn, error) {
    78  	if f.index < len(f.connErrPairs) {
    79  		index := f.index
    80  		connErr := f.connErrPairs[index]
    81  		f.index++
    82  		return connErr.conn, connErr.err
    83  	}
    84  	for {
    85  		if f.closed.Load() {
    86  			return nil, fmt.Errorf("use of closed network connection")
    87  		}
    88  	}
    89  }
    90  
    91  func (f *fakeListener) Close() error {
    92  	f.closed.Store(true)
    93  	return nil
    94  }
    95  
    96  func (f *fakeListener) Addr() net.Addr {
    97  	return f.addr
    98  }
    99  
   100  var _ net.Listener = &fakeListener{}
   101  
   102  func listenFuncFactory(listeners []*fakeListener) func(_ context.Context, network string, address string) (net.Listener, error) {
   103  	index := 0
   104  	return func(_ context.Context, network string, address string) (net.Listener, error) {
   105  		if index < len(listeners) {
   106  			host, portStr, err := net.SplitHostPort(address)
   107  			if err != nil {
   108  				return nil, err
   109  			}
   110  			port, err := strconv.Atoi(portStr)
   111  			if err != nil {
   112  				return nil, err
   113  			}
   114  			listener := listeners[index]
   115  			addr := &net.TCPAddr{
   116  				IP:   ParseIPSloppy(host),
   117  				Port: port,
   118  			}
   119  			if err != nil {
   120  				return nil, err
   121  			}
   122  			listener.addr = addr
   123  			index++
   124  
   125  			if listener.err != nil {
   126  				return nil, listener.err
   127  			}
   128  			return listener, nil
   129  		}
   130  		return nil, nil
   131  	}
   132  }
   133  
   134  func TestMultiListen(t *testing.T) {
   135  	testCases := []struct {
   136  		name          string
   137  		network       string
   138  		addrs         []string
   139  		fakeListeners []*fakeListener
   140  		errString     string
   141  	}{
   142  		{
   143  			name:      "unsupported network",
   144  			network:   "udp",
   145  			errString: "network \"udp\" not supported",
   146  		},
   147  		{
   148  			name:      "no host",
   149  			network:   "tcp",
   150  			errString: "no address provided to listen on",
   151  		},
   152  		{
   153  			name:          "valid",
   154  			network:       "tcp",
   155  			addrs:         []string{"127.0.0.1:12345"},
   156  			fakeListeners: []*fakeListener{{connErrPairs: []connErrPair{}}},
   157  		},
   158  	}
   159  
   160  	for _, tc := range testCases {
   161  		t.Run(tc.name, func(t *testing.T) {
   162  			ctx := context.TODO()
   163  			ml, err := multiListen(ctx, tc.network, tc.addrs, listenFuncFactory(tc.fakeListeners))
   164  
   165  			if tc.errString != "" {
   166  				assertError(t, tc.errString, err)
   167  			} else {
   168  				assertNoError(t, err)
   169  			}
   170  			if ml != nil {
   171  				err = ml.Close()
   172  				if err != nil {
   173  					t.Errorf("Did not expect error: %v", err)
   174  				}
   175  			}
   176  		})
   177  	}
   178  }
   179  
   180  func TestMultiListen_Addr(t *testing.T) {
   181  	ctx := context.TODO()
   182  	ml, err := multiListen(ctx, "tcp", []string{"10.10.10.10:5000", "192.168.1.10:5000", "127.0.0.1:5000"}, listenFuncFactory(
   183  		[]*fakeListener{{}, {}, {}},
   184  	))
   185  	if err != nil {
   186  		t.Errorf("Did not expect error: %v", err)
   187  	}
   188  
   189  	if ml.Addr().String() != "10.10.10.10:5000" {
   190  		t.Errorf("Expected '10.10.10.10:5000' but got '%s'", ml.Addr().String())
   191  	}
   192  
   193  	err = ml.Close()
   194  	if err != nil {
   195  		t.Errorf("Did not expect error: %v", err)
   196  	}
   197  }
   198  
   199  func TestMultiListen_Addrs(t *testing.T) {
   200  	ctx := context.TODO()
   201  	addrs := []string{"10.10.10.10:5000", "192.168.1.10:5000", "127.0.0.1:5000"}
   202  	ml, err := multiListen(ctx, "tcp", addrs, listenFuncFactory(
   203  		[]*fakeListener{{}, {}, {}},
   204  	))
   205  	if err != nil {
   206  		t.Errorf("Did not expect error: %v", err)
   207  	}
   208  
   209  	gotAddrs := ml.(*multiListener).Addrs()
   210  	for i := range gotAddrs {
   211  		if gotAddrs[i].String() != addrs[i] {
   212  			t.Errorf("expected %q; got %q", addrs[i], gotAddrs[i].String())
   213  		}
   214  
   215  	}
   216  
   217  	err = ml.Close()
   218  	if err != nil {
   219  		t.Errorf("Did not expect error: %v", err)
   220  	}
   221  }
   222  
   223  func TestMultiListen_Close(t *testing.T) {
   224  	testCases := []struct {
   225  		name          string
   226  		addrs         []string
   227  		runner        func(listener net.Listener, acceptCalls int) error
   228  		fakeListeners []*fakeListener
   229  		acceptCalls   int
   230  		errString     string
   231  	}{
   232  		{
   233  			name:  "close",
   234  			addrs: []string{"10.10.10.10:5000", "192.168.1.10:5000", "127.0.0.1:5000"},
   235  			runner: func(ml net.Listener, acceptCalls int) error {
   236  				for i := 0; i < acceptCalls; i++ {
   237  					_, err := ml.Accept()
   238  					if err != nil {
   239  						return err
   240  					}
   241  				}
   242  				err := ml.Close()
   243  				if err != nil {
   244  					return err
   245  				}
   246  				return nil
   247  			},
   248  			fakeListeners: []*fakeListener{{}, {}, {}},
   249  		},
   250  		{
   251  			name:  "close with pending connections",
   252  			addrs: []string{"10.10.10.10:5001", "192.168.1.10:5002", "127.0.0.1:5003"},
   253  			runner: func(ml net.Listener, acceptCalls int) error {
   254  				for i := 0; i < acceptCalls; i++ {
   255  					_, err := ml.Accept()
   256  					if err != nil {
   257  						return err
   258  					}
   259  				}
   260  				err := ml.Close()
   261  				if err != nil {
   262  					return err
   263  				}
   264  				return nil
   265  			},
   266  			fakeListeners: []*fakeListener{{
   267  				connErrPairs: []connErrPair{{
   268  					conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("10.10.10.10"), Port: 50001}},
   269  				}}}, {
   270  				connErrPairs: []connErrPair{{
   271  					conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("192.168.1.10"), Port: 50002}},
   272  				},
   273  				}}, {
   274  				connErrPairs: []connErrPair{{
   275  					conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 50003}},
   276  				}},
   277  			}},
   278  		},
   279  		{
   280  			name:  "close with no pending connections",
   281  			addrs: []string{"10.10.10.10:3001", "192.168.1.10:3002", "127.0.0.1:3003"},
   282  			runner: func(ml net.Listener, acceptCalls int) error {
   283  				for i := 0; i < acceptCalls; i++ {
   284  					_, err := ml.Accept()
   285  					if err != nil {
   286  						return err
   287  					}
   288  				}
   289  				err := ml.Close()
   290  				if err != nil {
   291  					return err
   292  				}
   293  				return nil
   294  			},
   295  			fakeListeners: []*fakeListener{{
   296  				connErrPairs: []connErrPair{
   297  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("10.10.10.10"), Port: 50001}}},
   298  				}}, {
   299  				connErrPairs: []connErrPair{
   300  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("192.168.1.10"), Port: 50002}}},
   301  				}}, {
   302  				connErrPairs: []connErrPair{
   303  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 50003}}},
   304  				},
   305  			}},
   306  			acceptCalls: 3,
   307  		},
   308  		{
   309  			name:  "close on close",
   310  			addrs: []string{"10.10.10.10:5000", "192.168.1.10:5000", "127.0.0.1:5000"},
   311  			runner: func(ml net.Listener, acceptCalls int) error {
   312  				for i := 0; i < acceptCalls; i++ {
   313  					_, err := ml.Accept()
   314  					if err != nil {
   315  						return err
   316  					}
   317  				}
   318  				err := ml.Close()
   319  				if err != nil {
   320  					return err
   321  				}
   322  
   323  				err = ml.Close()
   324  				if err != nil {
   325  					return err
   326  				}
   327  				return nil
   328  			},
   329  			fakeListeners: []*fakeListener{{}, {}, {}},
   330  			errString:     "use of closed network connection",
   331  		},
   332  	}
   333  
   334  	for _, tc := range testCases {
   335  		t.Run(tc.name, func(t *testing.T) {
   336  			ctx := context.TODO()
   337  			ml, err := multiListen(ctx, "tcp", tc.addrs, listenFuncFactory(tc.fakeListeners))
   338  			if err != nil {
   339  				t.Errorf("Did not expect error: %v", err)
   340  			}
   341  			err = tc.runner(ml, tc.acceptCalls)
   342  			if tc.errString != "" {
   343  				assertError(t, tc.errString, err)
   344  			} else {
   345  				assertNoError(t, err)
   346  			}
   347  
   348  			for _, f := range tc.fakeListeners {
   349  				if !f.closed.Load() {
   350  					t.Errorf("Expeted sub-listener to be closed")
   351  				}
   352  			}
   353  		})
   354  	}
   355  }
   356  
   357  func TestMultiListen_Accept(t *testing.T) {
   358  	testCases := []struct {
   359  		name          string
   360  		addrs         []string
   361  		runner        func(listener net.Listener, acceptCalls int) error
   362  		fakeListeners []*fakeListener
   363  		acceptCalls   int
   364  		errString     string
   365  	}{
   366  		{
   367  			name:  "accept all connections",
   368  			addrs: []string{"10.10.10.10:3000", "192.168.1.103:4000", "127.0.0.1:5000"},
   369  			runner: func(ml net.Listener, acceptCalls int) error {
   370  				for i := 0; i < acceptCalls; i++ {
   371  					_, err := ml.Accept()
   372  					if err != nil {
   373  						return err
   374  					}
   375  				}
   376  				err := ml.Close()
   377  				if err != nil {
   378  					return err
   379  				}
   380  				return nil
   381  			},
   382  			fakeListeners: []*fakeListener{{
   383  				connErrPairs: []connErrPair{
   384  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("10.10.10.10"), Port: 50001}}},
   385  				}}, {
   386  				connErrPairs: []connErrPair{
   387  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("192.168.1.10"), Port: 50002}}},
   388  				}}, {
   389  				connErrPairs: []connErrPair{
   390  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 50003}}},
   391  				},
   392  			}},
   393  			acceptCalls: 3,
   394  		},
   395  		{
   396  			name:  "accept some connections",
   397  			addrs: []string{"10.10.10.10:3000", "192.168.1.103:4000", "172.16.20.10:5000", "127.0.0.1:6000"},
   398  			runner: func(ml net.Listener, acceptCalls int) error {
   399  
   400  				for i := 0; i < acceptCalls; i++ {
   401  					_, err := ml.Accept()
   402  					if err != nil {
   403  						return err
   404  					}
   405  
   406  				}
   407  				err := ml.Close()
   408  				if err != nil {
   409  					return err
   410  				}
   411  				return nil
   412  			},
   413  			fakeListeners: []*fakeListener{{
   414  				connErrPairs: []connErrPair{
   415  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("10.10.10.10"), Port: 30001}}},
   416  				}}, {
   417  				connErrPairs: []connErrPair{
   418  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("192.168.1.10"), Port: 40001}}},
   419  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("192.168.1.10"), Port: 40002}}},
   420  				}}, {
   421  				connErrPairs: []connErrPair{
   422  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("172.16.20.10"), Port: 50001}}},
   423  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("172.16.20.10"), Port: 50002}}},
   424  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("172.16.20.10"), Port: 50003}}},
   425  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("172.16.20.10"), Port: 50004}}},
   426  				}}, {
   427  				connErrPairs: []connErrPair{
   428  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 60001}}},
   429  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 60002}}},
   430  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 60003}}},
   431  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 60004}}},
   432  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 60005}}},
   433  				},
   434  			}},
   435  			acceptCalls: 3,
   436  		},
   437  		{
   438  			name:  "accept on closed listener",
   439  			addrs: []string{"10.10.10.10:3001", "192.168.1.10:3002", "127.0.0.1:3003"},
   440  			runner: func(ml net.Listener, acceptCalls int) error {
   441  				err := ml.Close()
   442  				if err != nil {
   443  					return err
   444  				}
   445  				for i := 0; i < acceptCalls; i++ {
   446  					_, err := ml.Accept()
   447  					if err != nil {
   448  						return err
   449  					}
   450  				}
   451  				return nil
   452  			},
   453  			fakeListeners: []*fakeListener{{
   454  				connErrPairs: []connErrPair{
   455  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("10.10.10.10"), Port: 50001}}},
   456  				}}, {
   457  				connErrPairs: []connErrPair{
   458  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("192.168.1.10"), Port: 50002}}},
   459  				}}, {
   460  				connErrPairs: []connErrPair{
   461  					{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 50003}}},
   462  				},
   463  			}},
   464  			acceptCalls: 1,
   465  			errString:   "use of closed network connection",
   466  		},
   467  	}
   468  
   469  	for _, tc := range testCases {
   470  		t.Run(tc.name, func(t *testing.T) {
   471  			ctx := context.TODO()
   472  			ml, err := multiListen(ctx, "tcp", tc.addrs, listenFuncFactory(tc.fakeListeners))
   473  			if err != nil {
   474  				t.Errorf("Did not expect error: %v", err)
   475  			}
   476  
   477  			err = tc.runner(ml, tc.acceptCalls)
   478  			if tc.errString != "" {
   479  				assertError(t, tc.errString, err)
   480  			} else {
   481  				assertNoError(t, err)
   482  			}
   483  		})
   484  	}
   485  }
   486  
   487  func TestMultiListen_HTTP(t *testing.T) {
   488  	ctx := context.TODO()
   489  	ml, err := MultiListen(ctx, "tcp", ":0", ":0", ":0")
   490  	if err != nil {
   491  		t.Fatalf("unexpected error: %v", err)
   492  	}
   493  
   494  	addrs := ml.(*multiListener).Addrs()
   495  	if len(addrs) != 3 {
   496  		t.Fatalf("expected 3 listeners, got %v", addrs)
   497  	}
   498  
   499  	// serve http on multi-listener
   500  	handler := func(w http.ResponseWriter, _ *http.Request) {
   501  		io.WriteString(w, "hello")
   502  	}
   503  	server := http.Server{
   504  		Handler: http.HandlerFunc(handler),
   505  	}
   506  	go func() { _ = server.Serve(ml) }()
   507  	defer server.Close()
   508  
   509  	// Wait for server
   510  	awake := false
   511  	for i := 0; i < 5; i++ {
   512  		_, err = http.Get("http://" + addrs[0].String())
   513  		if err == nil {
   514  			awake = true
   515  			break
   516  		}
   517  		time.Sleep(50 * time.Millisecond)
   518  	}
   519  	if !awake {
   520  		t.Fatalf("http server did not respond in time")
   521  	}
   522  
   523  	// HTTP GET on each address.
   524  	for _, addr := range addrs {
   525  		_, err = http.Get("http://" + addr.String())
   526  		if err != nil {
   527  			t.Errorf("error connecting to %q: %v", addr.String(), err)
   528  		}
   529  	}
   530  }
   531  
   532  func assertError(t *testing.T, errString string, err error) {
   533  	if err == nil {
   534  		t.Errorf("Expected error '%s' but got none", errString)
   535  	}
   536  	if err.Error() != errString {
   537  		t.Errorf("Expected error '%s' but got '%s'", errString, err.Error())
   538  	}
   539  }
   540  
   541  func assertNoError(t *testing.T, err error) {
   542  	if err != nil {
   543  		t.Errorf("Did not expect error: %v", err)
   544  	}
   545  }
   546  

View as plain text