Copy util directory for use in cri/remote package

Signed-off-by: Davanum Srinivas <davanum@gmail.com>
This commit is contained in:
Davanum Srinivas 2020-04-28 07:44:27 -04:00
parent 0608e8be25
commit e049e594cd
No known key found for this signature in database
GPG Key ID: 80D83A796103BF59
11 changed files with 682 additions and 14 deletions

View File

@ -24,7 +24,7 @@ import (
"google.golang.org/grpc"
kubeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
apitest "k8s.io/cri-api/pkg/apis/testing"
"k8s.io/kubernetes/pkg/kubelet/util"
"k8s.io/kubernetes/pkg/kubelet/cri/remote/util"
utilexec "k8s.io/utils/exec"
)

View File

@ -27,7 +27,7 @@ import (
internalapi "k8s.io/cri-api/pkg/apis"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/kubernetes/pkg/kubelet/util"
"k8s.io/kubernetes/pkg/kubelet/cri/remote/util"
)
// RemoteImageService is a gRPC implementation of internalapi.ImageManagerService.

View File

@ -29,7 +29,7 @@ import (
"k8s.io/component-base/logs/logreduction"
internalapi "k8s.io/cri-api/pkg/apis"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/kubernetes/pkg/kubelet/util"
"k8s.io/kubernetes/pkg/kubelet/cri/remote/util"
utilexec "k8s.io/utils/exec"
)

View File

@ -0,0 +1,145 @@
// +build freebsd linux darwin
/*
Copyright 2017 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 (
"context"
"fmt"
"io/ioutil"
"net"
"net/url"
"os"
"path/filepath"
"golang.org/x/sys/unix"
"k8s.io/klog"
)
const (
// unixProtocol is the network protocol of unix socket.
unixProtocol = "unix"
)
// CreateListener creates a listener on the specified endpoint.
func CreateListener(endpoint string) (net.Listener, error) {
protocol, addr, err := parseEndpointWithFallbackProtocol(endpoint, unixProtocol)
if err != nil {
return nil, err
}
if protocol != unixProtocol {
return nil, fmt.Errorf("only support unix socket endpoint")
}
// Unlink to cleanup the previous socket file.
err = unix.Unlink(addr)
if err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("failed to unlink socket file %q: %v", addr, err)
}
if err := os.MkdirAll(filepath.Dir(addr), 0750); err != nil {
return nil, fmt.Errorf("error creating socket directory %q: %v", filepath.Dir(addr), err)
}
// Create the socket on a tempfile and move it to the destination socket to handle improprer cleanup
file, err := ioutil.TempFile(filepath.Dir(addr), "")
if err != nil {
return nil, fmt.Errorf("failed to create temporary file: %v", err)
}
if err := os.Remove(file.Name()); err != nil {
return nil, fmt.Errorf("failed to remove temporary file: %v", err)
}
l, err := net.Listen(protocol, file.Name())
if err != nil {
return nil, err
}
if err = os.Rename(file.Name(), addr); err != nil {
return nil, fmt.Errorf("failed to move temporary file to addr %q: %v", addr, err)
}
return l, nil
}
// GetAddressAndDialer returns the address parsed from the given endpoint and a context dialer.
func GetAddressAndDialer(endpoint string) (string, func(ctx context.Context, addr string) (net.Conn, error), error) {
protocol, addr, err := parseEndpointWithFallbackProtocol(endpoint, unixProtocol)
if err != nil {
return "", nil, err
}
if protocol != unixProtocol {
return "", nil, fmt.Errorf("only support unix socket endpoint")
}
return addr, dial, nil
}
func dial(ctx context.Context, addr string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, unixProtocol, addr)
}
func parseEndpointWithFallbackProtocol(endpoint string, fallbackProtocol string) (protocol string, addr string, err error) {
if protocol, addr, err = parseEndpoint(endpoint); err != nil && protocol == "" {
fallbackEndpoint := fallbackProtocol + "://" + endpoint
protocol, addr, err = parseEndpoint(fallbackEndpoint)
if err == nil {
klog.Warningf("Using %q as endpoint is deprecated, please consider using full url format %q.", endpoint, fallbackEndpoint)
}
}
return
}
func parseEndpoint(endpoint string) (string, string, error) {
u, err := url.Parse(endpoint)
if err != nil {
return "", "", err
}
switch u.Scheme {
case "tcp":
return "tcp", u.Host, nil
case "unix":
return "unix", u.Path, nil
case "":
return "", "", fmt.Errorf("using %q as endpoint is deprecated, please consider using full url format", endpoint)
default:
return u.Scheme, "", fmt.Errorf("protocol %q not supported", u.Scheme)
}
}
// IsUnixDomainSocket returns whether a given file is a AF_UNIX socket file
func IsUnixDomainSocket(filePath string) (bool, error) {
fi, err := os.Stat(filePath)
if err != nil {
return false, fmt.Errorf("stat file %s failed: %v", filePath, err)
}
if fi.Mode()&os.ModeSocket == 0 {
return false, nil
}
return true, nil
}
// NormalizePath is a no-op for Linux for now
func NormalizePath(path string) string {
return path
}

View File

@ -0,0 +1,135 @@
// +build freebsd linux darwin
/*
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 (
"io/ioutil"
"net"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseEndpoint(t *testing.T) {
tests := []struct {
endpoint string
expectError bool
expectedProtocol string
expectedAddr string
}{
{
endpoint: "unix:///tmp/s1.sock",
expectedProtocol: "unix",
expectedAddr: "/tmp/s1.sock",
},
{
endpoint: "tcp://localhost:15880",
expectedProtocol: "tcp",
expectedAddr: "localhost:15880",
},
{
endpoint: "npipe://./pipe/mypipe",
expectedProtocol: "npipe",
expectError: true,
},
{
endpoint: "tcp1://abc",
expectedProtocol: "tcp1",
expectError: true,
},
{
endpoint: "a b c",
expectError: true,
},
}
for _, test := range tests {
protocol, addr, err := parseEndpoint(test.endpoint)
assert.Equal(t, test.expectedProtocol, protocol)
if test.expectError {
assert.NotNil(t, err, "Expect error during parsing %q", test.endpoint)
continue
}
assert.Nil(t, err, "Expect no error during parsing %q", test.endpoint)
assert.Equal(t, test.expectedAddr, addr)
}
}
func TestIsUnixDomainSocket(t *testing.T) {
tests := []struct {
label string
listenOnSocket bool
expectSocket bool
expectError bool
invalidFile bool
}{
{
label: "Domain Socket file",
listenOnSocket: true,
expectSocket: true,
expectError: false,
},
{
label: "Non Existent file",
invalidFile: true,
expectError: true,
},
{
label: "Regular file",
listenOnSocket: false,
expectSocket: false,
expectError: false,
},
}
for _, test := range tests {
f, err := ioutil.TempFile("", "test-domain-socket")
require.NoErrorf(t, err, "Failed to create file for test purposes: %v while setting up: %s", err, test.label)
addr := f.Name()
f.Close()
var ln *net.UnixListener
if test.listenOnSocket {
os.Remove(addr)
ta, err := net.ResolveUnixAddr("unix", addr)
require.NoErrorf(t, err, "Failed to ResolveUnixAddr: %v while setting up: %s", err, test.label)
ln, err = net.ListenUnix("unix", ta)
require.NoErrorf(t, err, "Failed to ListenUnix: %v while setting up: %s", err, test.label)
}
fileToTest := addr
if test.invalidFile {
fileToTest = fileToTest + ".invalid"
}
result, err := IsUnixDomainSocket(fileToTest)
if test.listenOnSocket {
// this takes care of removing the file associated with the domain socket
ln.Close()
} else {
// explicitly remove regular file
os.Remove(addr)
}
if test.expectError {
assert.NotNil(t, err, "Unexpected nil error from IsUnixDomainSocket for %s", test.label)
} else {
assert.Nil(t, err, "Unexpected error invoking IsUnixDomainSocket for %s", test.label)
}
assert.Equal(t, result, test.expectSocket, "Unexpected result from IsUnixDomainSocket: %v for %s", result, test.label)
}
}

View File

@ -0,0 +1,55 @@
// +build !freebsd,!linux,!windows,!darwin
/*
Copyright 2017 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 (
"context"
"fmt"
"net"
"time"
)
// CreateListener creates a listener on the specified endpoint.
func CreateListener(endpoint string) (net.Listener, error) {
return nil, fmt.Errorf("CreateListener is unsupported in this build")
}
// GetAddressAndDialer returns the address parsed from the given endpoint and a context dialer.
func GetAddressAndDialer(endpoint string) (string, func(ctx context.Context, addr string) (net.Conn, error), error) {
return "", nil, fmt.Errorf("GetAddressAndDialer is unsupported in this build")
}
// LockAndCheckSubPath empty implementation
func LockAndCheckSubPath(volumePath, subPath string) ([]uintptr, error) {
return []uintptr{}, nil
}
// UnlockPath empty implementation
func UnlockPath(fileHandles []uintptr) {
}
// LocalEndpoint empty implementation
func LocalEndpoint(path, file string) (string, error) {
return "", fmt.Errorf("LocalEndpoints are unsupported in this build")
}
// GetBootTime empty implementation
func GetBootTime() (time.Time, error) {
return time.Time{}, fmt.Errorf("GetBootTime is unsupported in this build")
}

View File

@ -0,0 +1,149 @@
// +build windows
/*
Copyright 2017 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 (
"context"
"fmt"
"net"
"net/url"
"strings"
"syscall"
"time"
"github.com/Microsoft/go-winio"
)
const (
tcpProtocol = "tcp"
npipeProtocol = "npipe"
)
// CreateListener creates a listener on the specified endpoint.
func CreateListener(endpoint string) (net.Listener, error) {
protocol, addr, err := parseEndpoint(endpoint)
if err != nil {
return nil, err
}
switch protocol {
case tcpProtocol:
return net.Listen(tcpProtocol, addr)
case npipeProtocol:
return winio.ListenPipe(addr, nil)
default:
return nil, fmt.Errorf("only support tcp and npipe endpoint")
}
}
// GetAddressAndDialer returns the address parsed from the given endpoint and a context dialer.
func GetAddressAndDialer(endpoint string) (string, func(ctx context.Context, addr string) (net.Conn, error), error) {
protocol, addr, err := parseEndpoint(endpoint)
if err != nil {
return "", nil, err
}
if protocol == tcpProtocol {
return addr, tcpDial, nil
}
if protocol == npipeProtocol {
return addr, npipeDial, nil
}
return "", nil, fmt.Errorf("only support tcp and npipe endpoint")
}
func tcpDial(ctx context.Context, addr string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, tcpProtocol, addr)
}
func npipeDial(ctx context.Context, addr string) (net.Conn, error) {
return winio.DialPipeContext(ctx, addr)
}
func parseEndpoint(endpoint string) (string, string, error) {
// url.Parse doesn't recognize \, so replace with / first.
endpoint = strings.Replace(endpoint, "\\", "/", -1)
u, err := url.Parse(endpoint)
if err != nil {
return "", "", err
}
if u.Scheme == "tcp" {
return "tcp", u.Host, nil
} else if u.Scheme == "npipe" {
if strings.HasPrefix(u.Path, "//./pipe") {
return "npipe", u.Path, nil
}
// fallback host if not provided.
host := u.Host
if host == "" {
host = "."
}
return "npipe", fmt.Sprintf("//%s%s", host, u.Path), nil
} else if u.Scheme == "" {
return "", "", fmt.Errorf("Using %q as endpoint is deprecated, please consider using full url format", endpoint)
} else {
return u.Scheme, "", fmt.Errorf("protocol %q not supported", u.Scheme)
}
}
var tickCount = syscall.NewLazyDLL("kernel32.dll").NewProc("GetTickCount64")
// GetBootTime returns the time at which the machine was started, truncated to the nearest second
func GetBootTime() (time.Time, error) {
currentTime := time.Now()
output, _, err := tickCount.Call()
if errno, ok := err.(syscall.Errno); !ok || errno != 0 {
return time.Time{}, err
}
return currentTime.Add(-time.Duration(output) * time.Millisecond).Truncate(time.Second), nil
}
// IsUnixDomainSocket returns whether a given file is a AF_UNIX socket file
func IsUnixDomainSocket(filePath string) (bool, error) {
// Due to the absence of golang support for os.ModeSocket in Windows (https://github.com/golang/go/issues/33357)
// we need to dial the file and check if we receive an error to determine if a file is Unix Domain Socket file.
// Note that querrying for the Reparse Points (https://docs.microsoft.com/en-us/windows/win32/fileio/reparse-points)
// for the file (using FSCTL_GET_REPARSE_POINT) and checking for reparse tag: reparseTagSocket
// does NOT work in 1809 if the socket file is created within a bind mounted directory by a container
// and the FSCTL is issued in the host by the kubelet.
c, err := net.Dial("unix", filePath)
if err == nil {
c.Close()
return true, nil
}
return false, nil
}
// NormalizePath converts FS paths returned by certain go frameworks (like fsnotify)
// to native Windows paths that can be passed to Windows specific code
func NormalizePath(path string) string {
path = strings.ReplaceAll(path, "/", "\\")
if strings.HasPrefix(path, "\\") {
path = "c:" + path
}
return path
}

View File

@ -0,0 +1,195 @@
// +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 (
"io/ioutil"
"math/rand"
"net"
"os"
"testing"
"time"
winio "github.com/Microsoft/go-winio"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseEndpoint(t *testing.T) {
tests := []struct {
endpoint string
expectError bool
expectedProtocol string
expectedAddr string
}{
{
endpoint: "unix:///tmp/s1.sock",
expectedProtocol: "unix",
expectError: 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",
expectError: true,
},
{
endpoint: "a b c",
expectError: true,
},
}
for _, test := range tests {
protocol, addr, err := parseEndpoint(test.endpoint)
assert.Equal(t, test.expectedProtocol, protocol)
if test.expectError {
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)
}
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")
}
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))
}
}

View File

@ -1,6 +0,0 @@
rules:
# prevent exposing internal api in streaming packages
- selectorRegexp: k8s[.]io/kubernetes
allowedPrefixes:
- k8s.io/kubernetes/pkg/kubelet/server/portforward
- k8s.io/kubernetes/pkg/kubelet/server/remotecommand

View File

@ -1,5 +0,0 @@
rules:
# prevent exposing internal api in streaming packages
- selectorRegexp: k8s[.]io/kubernetes
forbiddenPrefixes:
- ''