mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			321 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			321 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| //go:build windows
 | |
| // +build windows
 | |
| 
 | |
| /*
 | |
| Copyright 2018 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package util
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"math/rand"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"reflect"
 | |
| 	"runtime"
 | |
| 	"sync"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	winio "github.com/Microsoft/go-winio"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| func TestGetAddressAndDialer(t *testing.T) {
 | |
| 
 | |
| 	// Compare dialer function by pointer
 | |
| 	tcpDialPointer := reflect.ValueOf(tcpDial).Pointer()
 | |
| 	npipeDialPointer := reflect.ValueOf(npipeDial).Pointer()
 | |
| 	var nilDialPointer uintptr = 0x0
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		endpoint      string
 | |
| 		expectedAddr  string
 | |
| 		expectedDial  uintptr
 | |
| 		expectedError bool
 | |
| 	}{
 | |
| 		{
 | |
| 			endpoint:      "tcp://localhost:15880",
 | |
| 			expectedAddr:  "localhost:15880",
 | |
| 			expectedDial:  tcpDialPointer,
 | |
| 			expectedError: false,
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:      "npipe://./pipe/mypipe",
 | |
| 			expectedAddr:  "//./pipe/mypipe",
 | |
| 			expectedDial:  npipeDialPointer,
 | |
| 			expectedError: false,
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:      "npipe:\\\\.\\pipe\\mypipe",
 | |
| 			expectedAddr:  "//./pipe/mypipe",
 | |
| 			expectedDial:  npipeDialPointer,
 | |
| 			expectedError: false,
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:      "unix:///tmp/s1.sock",
 | |
| 			expectedAddr:  "",
 | |
| 			expectedDial:  nilDialPointer,
 | |
| 			expectedError: true,
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:      "tcp1://abc",
 | |
| 			expectedAddr:  "",
 | |
| 			expectedDial:  nilDialPointer,
 | |
| 			expectedError: true,
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:      "a b c",
 | |
| 			expectedAddr:  "",
 | |
| 			expectedDial:  nilDialPointer,
 | |
| 			expectedError: true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		expectedDialerName := runtime.FuncForPC(test.expectedDial).Name()
 | |
| 		if expectedDialerName == "" {
 | |
| 			expectedDialerName = "nilDial"
 | |
| 		}
 | |
| 		t.Run(fmt.Sprintf("Endpoint is %s, addr is %s and dialer is %s",
 | |
| 			test.endpoint, test.expectedAddr, expectedDialerName),
 | |
| 			func(t *testing.T) {
 | |
| 				address, dialer, err := GetAddressAndDialer(test.endpoint)
 | |
| 
 | |
| 				dialerPointer := reflect.ValueOf(dialer).Pointer()
 | |
| 				actualDialerName := runtime.FuncForPC(dialerPointer).Name()
 | |
| 				if actualDialerName == "" {
 | |
| 					actualDialerName = "nilDial"
 | |
| 				}
 | |
| 
 | |
| 				assert.Equalf(t, test.expectedDial, dialerPointer,
 | |
| 					"Expected dialer %s, but get %s", expectedDialerName, actualDialerName)
 | |
| 
 | |
| 				assert.Equal(t, test.expectedAddr, address)
 | |
| 
 | |
| 				if test.expectedError {
 | |
| 					assert.NotNil(t, err, "Expect error during parsing %q", test.endpoint)
 | |
| 				} else {
 | |
| 					assert.Nil(t, err, "Expect no error during parsing %q", test.endpoint)
 | |
| 				}
 | |
| 			})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestParseEndpoint(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		endpoint         string
 | |
| 		expectedError    bool
 | |
| 		expectedProtocol string
 | |
| 		expectedAddr     string
 | |
| 	}{
 | |
| 		{
 | |
| 			endpoint:         "unix:///tmp/s1.sock",
 | |
| 			expectedProtocol: "unix",
 | |
| 			expectedError:    true,
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:         "tcp://localhost:15880",
 | |
| 			expectedProtocol: "tcp",
 | |
| 			expectedAddr:     "localhost:15880",
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:         "npipe://./pipe/mypipe",
 | |
| 			expectedProtocol: "npipe",
 | |
| 			expectedAddr:     "//./pipe/mypipe",
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:         "npipe:////./pipe/mypipe2",
 | |
| 			expectedProtocol: "npipe",
 | |
| 			expectedAddr:     "//./pipe/mypipe2",
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:         "npipe:/pipe/mypipe3",
 | |
| 			expectedProtocol: "npipe",
 | |
| 			expectedAddr:     "//./pipe/mypipe3",
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:         "npipe:\\\\.\\pipe\\mypipe4",
 | |
| 			expectedProtocol: "npipe",
 | |
| 			expectedAddr:     "//./pipe/mypipe4",
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:         "npipe:\\pipe\\mypipe5",
 | |
| 			expectedProtocol: "npipe",
 | |
| 			expectedAddr:     "//./pipe/mypipe5",
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:         "tcp1://abc",
 | |
| 			expectedProtocol: "tcp1",
 | |
| 			expectedError:    true,
 | |
| 		},
 | |
| 		{
 | |
| 			endpoint:      "a b c",
 | |
| 			expectedError: true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		protocol, addr, err := parseEndpoint(test.endpoint)
 | |
| 		assert.Equal(t, test.expectedProtocol, protocol)
 | |
| 		if test.expectedError {
 | |
| 			assert.NotNil(t, err, "Expect error during parsing %q", test.endpoint)
 | |
| 			continue
 | |
| 		}
 | |
| 		require.Nil(t, err, "Expect no error during parsing %q", test.endpoint)
 | |
| 		assert.Equal(t, test.expectedAddr, addr)
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| func testPipe(t *testing.T, label string) {
 | |
| 	generatePipeName := func(suffixLen int) string {
 | |
| 		rand.Seed(time.Now().UnixNano())
 | |
| 		letter := []rune("abcdef0123456789")
 | |
| 		b := make([]rune, suffixLen)
 | |
| 		for i := range b {
 | |
| 			b[i] = letter[rand.Intn(len(letter))]
 | |
| 		}
 | |
| 		return "\\\\.\\pipe\\test-pipe" + string(b)
 | |
| 	}
 | |
| 	testFile := generatePipeName(4)
 | |
| 	pipeln, err := winio.ListenPipe(testFile, &winio.PipeConfig{SecurityDescriptor: "D:P(A;;GA;;;BA)(A;;GA;;;SY)"})
 | |
| 	defer pipeln.Close()
 | |
| 
 | |
| 	require.NoErrorf(t, err, "Failed to listen on named pipe for test purposes: %v while setting up: %s", err, label)
 | |
| 	result, err := IsUnixDomainSocket(testFile)
 | |
| 	assert.Nil(t, err, "Unexpected error: %v from IsUnixDomainSocket for %s", err, label)
 | |
| 	assert.False(t, result, "Unexpected result: true from IsUnixDomainSocket: %v for %s", result, label)
 | |
| }
 | |
| 
 | |
| func testRegularFile(t *testing.T, label string, exists bool) {
 | |
| 	f, err := ioutil.TempFile("", "test-file")
 | |
| 	require.NoErrorf(t, err, "Failed to create file for test purposes: %v while setting up: %s", err, label)
 | |
| 	testFile := f.Name()
 | |
| 	if !exists {
 | |
| 		testFile = testFile + ".absent"
 | |
| 	}
 | |
| 	f.Close()
 | |
| 	result, err := IsUnixDomainSocket(testFile)
 | |
| 	os.Remove(f.Name())
 | |
| 	assert.Nil(t, err, "Unexpected error: %v from IsUnixDomainSocket for %s", err, label)
 | |
| 	assert.False(t, result, "Unexpected result: true from IsUnixDomainSocket: %v for %s", result, label)
 | |
| }
 | |
| 
 | |
| func testUnixDomainSocket(t *testing.T, label string) {
 | |
| 	f, err := ioutil.TempFile("", "test-domain-socket")
 | |
| 	require.NoErrorf(t, err, "Failed to create file for test purposes: %v while setting up: %s", err, label)
 | |
| 	testFile := f.Name()
 | |
| 	f.Close()
 | |
| 	os.Remove(testFile)
 | |
| 	ta, err := net.ResolveUnixAddr("unix", testFile)
 | |
| 	require.NoErrorf(t, err, "Failed to ResolveUnixAddr: %v while setting up: %s", err, label)
 | |
| 	unixln, err := net.ListenUnix("unix", ta)
 | |
| 	require.NoErrorf(t, err, "Failed to ListenUnix: %v while setting up: %s", err, label)
 | |
| 	result, err := IsUnixDomainSocket(testFile)
 | |
| 	unixln.Close()
 | |
| 	assert.Nil(t, err, "Unexpected error: %v from IsUnixDomainSocket for %s", err, label)
 | |
| 	assert.True(t, result, "Unexpected result: false from IsUnixDomainSocket: %v for %s", result, label)
 | |
| }
 | |
| 
 | |
| // This is required as on Windows it's possible for the socket file backing a Unix domain socket to
 | |
| // exist but not be ready for socket communications yet as per
 | |
| // https://github.com/kubernetes/kubernetes/issues/104584
 | |
| func testPendingUnixDomainSocket(t *testing.T, label string) {
 | |
| 	// Create a temporary file that will simulate the Unix domain socket file in a
 | |
| 	// not-yet-ready state. We need this because the Kubelet keeps an eye on file
 | |
| 	// changes and acts on them, leading to potential race issues as described in
 | |
| 	// the referenced issue above
 | |
| 	f, err := ioutil.TempFile("", "test-domain-socket")
 | |
| 	require.NoErrorf(t, err, "Failed to create file for test purposes: %v while setting up: %s", err, label)
 | |
| 	testFile := f.Name()
 | |
| 	f.Close()
 | |
| 
 | |
| 	// Start the check at this point
 | |
| 	wg := sync.WaitGroup{}
 | |
| 	wg.Add(1)
 | |
| 	go func() {
 | |
| 		result, err := IsUnixDomainSocket(testFile)
 | |
| 		assert.Nil(t, err, "Unexpected error: %v from IsUnixDomainSocket for %s", err, label)
 | |
| 		assert.True(t, result, "Unexpected result: false from IsUnixDomainSocket: %v for %s", result, label)
 | |
| 		wg.Done()
 | |
| 	}()
 | |
| 
 | |
| 	// Wait a sufficient amount of time to make sure the retry logic kicks in
 | |
| 	time.Sleep(socketDialRetryPeriod)
 | |
| 
 | |
| 	// Replace the temporary file with an actual Unix domain socket file
 | |
| 	os.Remove(testFile)
 | |
| 	ta, err := net.ResolveUnixAddr("unix", testFile)
 | |
| 	require.NoErrorf(t, err, "Failed to ResolveUnixAddr: %v while setting up: %s", err, label)
 | |
| 	unixln, err := net.ListenUnix("unix", ta)
 | |
| 	require.NoErrorf(t, err, "Failed to ListenUnix: %v while setting up: %s", err, label)
 | |
| 
 | |
| 	// Wait for the goroutine to finish, then close the socket
 | |
| 	wg.Wait()
 | |
| 	unixln.Close()
 | |
| }
 | |
| 
 | |
| func TestIsUnixDomainSocket(t *testing.T) {
 | |
| 	testPipe(t, "Named Pipe")
 | |
| 	testRegularFile(t, "Regular File that Exists", true)
 | |
| 	testRegularFile(t, "Regular File that Does Not Exist", false)
 | |
| 	testUnixDomainSocket(t, "Unix Domain Socket File")
 | |
| 	testPendingUnixDomainSocket(t, "Pending Unix Domain Socket File")
 | |
| }
 | |
| 
 | |
| func TestNormalizePath(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		originalpath   string
 | |
| 		normalizedPath string
 | |
| 	}{
 | |
| 		{
 | |
| 			originalpath:   "\\path\\to\\file",
 | |
| 			normalizedPath: "c:\\path\\to\\file",
 | |
| 		},
 | |
| 		{
 | |
| 			originalpath:   "/path/to/file",
 | |
| 			normalizedPath: "c:\\path\\to\\file",
 | |
| 		},
 | |
| 		{
 | |
| 			originalpath:   "/path/to/dir/",
 | |
| 			normalizedPath: "c:\\path\\to\\dir\\",
 | |
| 		},
 | |
| 		{
 | |
| 			originalpath:   "\\path\\to\\dir\\",
 | |
| 			normalizedPath: "c:\\path\\to\\dir\\",
 | |
| 		},
 | |
| 		{
 | |
| 			originalpath:   "/file",
 | |
| 			normalizedPath: "c:\\file",
 | |
| 		},
 | |
| 		{
 | |
| 			originalpath:   "\\file",
 | |
| 			normalizedPath: "c:\\file",
 | |
| 		},
 | |
| 		{
 | |
| 			originalpath:   "fileonly",
 | |
| 			normalizedPath: "fileonly",
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		assert.Equal(t, test.normalizedPath, NormalizePath(test.originalpath))
 | |
| 	}
 | |
| }
 |