682 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			682 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
   Copyright The containerd 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 integration
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"flag"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	goruntime "runtime"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"syscall"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/containerd/containerd"
 | 
						|
	"github.com/containerd/containerd/containers"
 | 
						|
	cri "github.com/containerd/containerd/integration/cri-api/pkg/apis"
 | 
						|
	_ "github.com/containerd/containerd/integration/images" // Keep this around to parse `imageListFile` command line var
 | 
						|
	"github.com/containerd/containerd/integration/remote"
 | 
						|
	dialer "github.com/containerd/containerd/integration/remote/util"
 | 
						|
	criconfig "github.com/containerd/containerd/pkg/cri/config"
 | 
						|
	"github.com/containerd/containerd/pkg/cri/constants"
 | 
						|
	"github.com/containerd/containerd/pkg/cri/server"
 | 
						|
	"github.com/containerd/containerd/pkg/cri/util"
 | 
						|
	"github.com/opencontainers/selinux/go-selinux"
 | 
						|
	"github.com/sirupsen/logrus"
 | 
						|
	"github.com/stretchr/testify/assert"
 | 
						|
	"github.com/stretchr/testify/require"
 | 
						|
	exec "golang.org/x/sys/execabs"
 | 
						|
	"google.golang.org/grpc"
 | 
						|
	"google.golang.org/grpc/credentials/insecure"
 | 
						|
	runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	timeout      = 1 * time.Minute
 | 
						|
	k8sNamespace = constants.K8sContainerdNamespace
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	runtimeService     cri.RuntimeService
 | 
						|
	imageService       cri.ImageManagerService
 | 
						|
	containerdClient   *containerd.Client
 | 
						|
	containerdEndpoint string
 | 
						|
)
 | 
						|
 | 
						|
var criEndpoint = flag.String("cri-endpoint", "unix:///run/containerd/containerd.sock", "The endpoint of cri plugin.")
 | 
						|
var criRoot = flag.String("cri-root", "/var/lib/containerd/io.containerd.grpc.v1.cri", "The root directory of cri plugin.")
 | 
						|
var runtimeHandler = flag.String("runtime-handler", "", "The runtime handler to use in the test.")
 | 
						|
var containerdBin = flag.String("containerd-bin", "containerd", "The containerd binary name. The name is used to restart containerd during test.")
 | 
						|
 | 
						|
func TestMain(m *testing.M) {
 | 
						|
	flag.Parse()
 | 
						|
	if err := ConnectDaemons(); err != nil {
 | 
						|
		logrus.WithError(err).Fatalf("Failed to connect daemons")
 | 
						|
	}
 | 
						|
	os.Exit(m.Run())
 | 
						|
}
 | 
						|
 | 
						|
// ConnectDaemons connect cri plugin and containerd, and initialize the clients.
 | 
						|
func ConnectDaemons() error {
 | 
						|
	var err error
 | 
						|
	runtimeService, err = remote.NewRuntimeService(*criEndpoint, timeout)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to create runtime service: %w", err)
 | 
						|
	}
 | 
						|
	imageService, err = remote.NewImageService(*criEndpoint, timeout)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to create image service: %w", err)
 | 
						|
	}
 | 
						|
	// Since CRI grpc client doesn't have `WithBlock` specified, we
 | 
						|
	// need to check whether it is actually connected.
 | 
						|
	// TODO(#6069) Use grpc options to block on connect and remove for this list containers request.
 | 
						|
	_, err = runtimeService.ListContainers(&runtime.ContainerFilter{})
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to list containers: %w", err)
 | 
						|
	}
 | 
						|
	_, err = imageService.ListImages(&runtime.ImageFilter{})
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to list images: %w", err)
 | 
						|
	}
 | 
						|
	// containerdEndpoint is the same with criEndpoint now
 | 
						|
	containerdEndpoint = strings.TrimPrefix(*criEndpoint, "unix://")
 | 
						|
	containerdEndpoint = strings.TrimPrefix(containerdEndpoint, "npipe:")
 | 
						|
	containerdClient, err = containerd.New(containerdEndpoint, containerd.WithDefaultNamespace(k8sNamespace))
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to connect containerd: %w", err)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Opts sets specific information in pod sandbox config.
 | 
						|
type PodSandboxOpts func(*runtime.PodSandboxConfig)
 | 
						|
 | 
						|
// Set host network.
 | 
						|
func WithHostNetwork(p *runtime.PodSandboxConfig) {
 | 
						|
	if p.Linux == nil {
 | 
						|
		p.Linux = &runtime.LinuxPodSandboxConfig{}
 | 
						|
	}
 | 
						|
	if p.Linux.SecurityContext == nil {
 | 
						|
		p.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{}
 | 
						|
	}
 | 
						|
	if p.Linux.SecurityContext.NamespaceOptions == nil {
 | 
						|
		p.Linux.SecurityContext.NamespaceOptions = &runtime.NamespaceOption{}
 | 
						|
	}
 | 
						|
	p.Linux.SecurityContext.NamespaceOptions.Network = runtime.NamespaceMode_NODE
 | 
						|
}
 | 
						|
 | 
						|
// Set pod userns.
 | 
						|
func WithPodUserNs(containerID, hostID, length uint32) PodSandboxOpts {
 | 
						|
	return func(p *runtime.PodSandboxConfig) {
 | 
						|
		if p.Linux == nil {
 | 
						|
			p.Linux = &runtime.LinuxPodSandboxConfig{}
 | 
						|
		}
 | 
						|
		if p.Linux.SecurityContext == nil {
 | 
						|
			p.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{}
 | 
						|
		}
 | 
						|
		if p.Linux.SecurityContext.NamespaceOptions == nil {
 | 
						|
			p.Linux.SecurityContext.NamespaceOptions = &runtime.NamespaceOption{}
 | 
						|
		}
 | 
						|
 | 
						|
		idMap := runtime.IDMapping{
 | 
						|
			HostId:      hostID,
 | 
						|
			ContainerId: containerID,
 | 
						|
			Length:      length,
 | 
						|
		}
 | 
						|
		if p.Linux.SecurityContext.NamespaceOptions.UsernsOptions == nil {
 | 
						|
			p.Linux.SecurityContext.NamespaceOptions.UsernsOptions = &runtime.UserNamespace{
 | 
						|
				Mode: runtime.NamespaceMode_POD,
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		p.Linux.SecurityContext.NamespaceOptions.UsernsOptions.Uids = append(p.Linux.SecurityContext.NamespaceOptions.UsernsOptions.Uids, &idMap)
 | 
						|
		p.Linux.SecurityContext.NamespaceOptions.UsernsOptions.Gids = append(p.Linux.SecurityContext.NamespaceOptions.UsernsOptions.Gids, &idMap)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Set host pid.
 | 
						|
func WithHostPid(p *runtime.PodSandboxConfig) {
 | 
						|
	if p.Linux == nil {
 | 
						|
		p.Linux = &runtime.LinuxPodSandboxConfig{}
 | 
						|
	}
 | 
						|
	if p.Linux.SecurityContext == nil {
 | 
						|
		p.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{}
 | 
						|
	}
 | 
						|
	if p.Linux.SecurityContext.NamespaceOptions == nil {
 | 
						|
		p.Linux.SecurityContext.NamespaceOptions = &runtime.NamespaceOption{}
 | 
						|
	}
 | 
						|
	p.Linux.SecurityContext.NamespaceOptions.Pid = runtime.NamespaceMode_NODE
 | 
						|
}
 | 
						|
 | 
						|
// Set pod pid.
 | 
						|
func WithPodPid(p *runtime.PodSandboxConfig) {
 | 
						|
	if p.Linux == nil {
 | 
						|
		p.Linux = &runtime.LinuxPodSandboxConfig{}
 | 
						|
	}
 | 
						|
	if p.Linux.SecurityContext == nil {
 | 
						|
		p.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{}
 | 
						|
	}
 | 
						|
	if p.Linux.SecurityContext.NamespaceOptions == nil {
 | 
						|
		p.Linux.SecurityContext.NamespaceOptions = &runtime.NamespaceOption{}
 | 
						|
	}
 | 
						|
	p.Linux.SecurityContext.NamespaceOptions.Pid = runtime.NamespaceMode_POD
 | 
						|
}
 | 
						|
 | 
						|
// Add pod log directory.
 | 
						|
func WithPodLogDirectory(dir string) PodSandboxOpts {
 | 
						|
	return func(p *runtime.PodSandboxConfig) {
 | 
						|
		p.LogDirectory = dir
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Add pod hostname.
 | 
						|
func WithPodHostname(hostname string) PodSandboxOpts {
 | 
						|
	return func(p *runtime.PodSandboxConfig) {
 | 
						|
		p.Hostname = hostname
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Add pod labels.
 | 
						|
func WithPodLabels(kvs map[string]string) PodSandboxOpts {
 | 
						|
	return func(p *runtime.PodSandboxConfig) {
 | 
						|
		for k, v := range kvs {
 | 
						|
			p.Labels[k] = v
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// PodSandboxConfig generates a pod sandbox config for test.
 | 
						|
func PodSandboxConfig(name, ns string, opts ...PodSandboxOpts) *runtime.PodSandboxConfig {
 | 
						|
	config := &runtime.PodSandboxConfig{
 | 
						|
		Metadata: &runtime.PodSandboxMetadata{
 | 
						|
			Name: name,
 | 
						|
			// Using random id as uuid is good enough for local
 | 
						|
			// integration test.
 | 
						|
			Uid:       util.GenerateID(),
 | 
						|
			Namespace: Randomize(ns),
 | 
						|
		},
 | 
						|
		Linux:       &runtime.LinuxPodSandboxConfig{},
 | 
						|
		Annotations: make(map[string]string),
 | 
						|
		Labels:      make(map[string]string),
 | 
						|
	}
 | 
						|
	for _, opt := range opts {
 | 
						|
		opt(config)
 | 
						|
	}
 | 
						|
	return config
 | 
						|
}
 | 
						|
 | 
						|
func PodSandboxConfigWithCleanup(t *testing.T, name, ns string, opts ...PodSandboxOpts) (string, *runtime.PodSandboxConfig) {
 | 
						|
	sbConfig := PodSandboxConfig(name, ns, opts...)
 | 
						|
	sb, err := runtimeService.RunPodSandbox(sbConfig, *runtimeHandler)
 | 
						|
	require.NoError(t, err)
 | 
						|
	t.Cleanup(func() {
 | 
						|
		assert.NoError(t, runtimeService.StopPodSandbox(sb))
 | 
						|
		assert.NoError(t, runtimeService.RemovePodSandbox(sb))
 | 
						|
	})
 | 
						|
 | 
						|
	return sb, sbConfig
 | 
						|
}
 | 
						|
 | 
						|
// Set Windows HostProcess on the pod.
 | 
						|
func WithWindowsHostProcessPod(p *runtime.PodSandboxConfig) {
 | 
						|
	if p.Windows == nil {
 | 
						|
		p.Windows = &runtime.WindowsPodSandboxConfig{}
 | 
						|
	}
 | 
						|
	if p.Windows.SecurityContext == nil {
 | 
						|
		p.Windows.SecurityContext = &runtime.WindowsSandboxSecurityContext{}
 | 
						|
	}
 | 
						|
	p.Windows.SecurityContext.HostProcess = true
 | 
						|
}
 | 
						|
 | 
						|
// ContainerOpts to set any specific attribute like labels,
 | 
						|
// annotations, metadata etc
 | 
						|
type ContainerOpts func(*runtime.ContainerConfig)
 | 
						|
 | 
						|
func WithTestLabels() ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		c.Labels = map[string]string{"key": "value"}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func WithTestAnnotations() ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		c.Annotations = map[string]string{"a.b.c": "test"}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Add container resource limits.
 | 
						|
func WithResources(r *runtime.LinuxContainerResources) ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		if c.Linux == nil {
 | 
						|
			c.Linux = &runtime.LinuxContainerConfig{}
 | 
						|
		}
 | 
						|
		c.Linux.Resources = r
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Adds Windows container resource limits.
 | 
						|
func WithWindowsResources(r *runtime.WindowsContainerResources) ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		if c.Windows == nil {
 | 
						|
			c.Windows = &runtime.WindowsContainerConfig{}
 | 
						|
		}
 | 
						|
		c.Windows.Resources = r
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func WithVolumeMount(hostPath, containerPath string) ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		hostPath, _ = filepath.Abs(hostPath)
 | 
						|
		containerPath, _ = filepath.Abs(containerPath)
 | 
						|
		mount := &runtime.Mount{
 | 
						|
			HostPath:       hostPath,
 | 
						|
			ContainerPath:  containerPath,
 | 
						|
			SelinuxRelabel: selinux.GetEnabled(),
 | 
						|
		}
 | 
						|
		c.Mounts = append(c.Mounts, mount)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func WithWindowsUsername(username string) ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		if c.Windows == nil {
 | 
						|
			c.Windows = &runtime.WindowsContainerConfig{}
 | 
						|
		}
 | 
						|
		if c.Windows.SecurityContext == nil {
 | 
						|
			c.Windows.SecurityContext = &runtime.WindowsContainerSecurityContext{}
 | 
						|
		}
 | 
						|
		c.Windows.SecurityContext.RunAsUsername = username
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func WithWindowsHostProcessContainer() ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		if c.Windows == nil {
 | 
						|
			c.Windows = &runtime.WindowsContainerConfig{}
 | 
						|
		}
 | 
						|
		if c.Windows.SecurityContext == nil {
 | 
						|
			c.Windows.SecurityContext = &runtime.WindowsContainerSecurityContext{}
 | 
						|
		}
 | 
						|
		c.Windows.SecurityContext.HostProcess = true
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Add container command.
 | 
						|
func WithCommand(cmd string, args ...string) ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		c.Command = []string{cmd}
 | 
						|
		c.Args = args
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Add pid namespace mode.
 | 
						|
func WithPidNamespace(mode runtime.NamespaceMode) ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		if c.Linux == nil {
 | 
						|
			c.Linux = &runtime.LinuxContainerConfig{}
 | 
						|
		}
 | 
						|
		if c.Linux.SecurityContext == nil {
 | 
						|
			c.Linux.SecurityContext = &runtime.LinuxContainerSecurityContext{}
 | 
						|
		}
 | 
						|
		if c.Linux.SecurityContext.NamespaceOptions == nil {
 | 
						|
			c.Linux.SecurityContext.NamespaceOptions = &runtime.NamespaceOption{}
 | 
						|
		}
 | 
						|
		c.Linux.SecurityContext.NamespaceOptions.Pid = mode
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
// Add user namespace pod mode.
 | 
						|
func WithUserNamespace(containerID, hostID, length uint32) ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		if c.Linux == nil {
 | 
						|
			c.Linux = &runtime.LinuxContainerConfig{}
 | 
						|
		}
 | 
						|
		if c.Linux.SecurityContext == nil {
 | 
						|
			c.Linux.SecurityContext = &runtime.LinuxContainerSecurityContext{}
 | 
						|
		}
 | 
						|
		if c.Linux.SecurityContext.NamespaceOptions == nil {
 | 
						|
			c.Linux.SecurityContext.NamespaceOptions = &runtime.NamespaceOption{}
 | 
						|
		}
 | 
						|
		idMap := runtime.IDMapping{
 | 
						|
			HostId:      hostID,
 | 
						|
			ContainerId: containerID,
 | 
						|
			Length:      length,
 | 
						|
		}
 | 
						|
 | 
						|
		if c.Linux.SecurityContext.NamespaceOptions.UsernsOptions == nil {
 | 
						|
			c.Linux.SecurityContext.NamespaceOptions.UsernsOptions = &runtime.UserNamespace{
 | 
						|
				Mode: runtime.NamespaceMode_POD,
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		c.Linux.SecurityContext.NamespaceOptions.UsernsOptions.Uids = append(c.Linux.SecurityContext.NamespaceOptions.UsernsOptions.Uids, &idMap)
 | 
						|
		c.Linux.SecurityContext.NamespaceOptions.UsernsOptions.Gids = append(c.Linux.SecurityContext.NamespaceOptions.UsernsOptions.Gids, &idMap)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Add container log path.
 | 
						|
func WithLogPath(path string) ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		c.LogPath = path
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithSupplementalGroups adds supplemental groups.
 | 
						|
func WithSupplementalGroups(gids []int64) ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		if c.Linux == nil {
 | 
						|
			c.Linux = &runtime.LinuxContainerConfig{}
 | 
						|
		}
 | 
						|
		if c.Linux.SecurityContext == nil {
 | 
						|
			c.Linux.SecurityContext = &runtime.LinuxContainerSecurityContext{}
 | 
						|
		}
 | 
						|
		c.Linux.SecurityContext.SupplementalGroups = gids
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithDevice adds a device mount.
 | 
						|
func WithDevice(containerPath, hostPath, permissions string) ContainerOpts {
 | 
						|
	return func(c *runtime.ContainerConfig) {
 | 
						|
		c.Devices = append(c.Devices, &runtime.Device{
 | 
						|
			ContainerPath: containerPath, HostPath: hostPath, Permissions: permissions,
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ContainerConfig creates a container config given a name and image name
 | 
						|
// and additional container config options
 | 
						|
func ContainerConfig(name, image string, opts ...ContainerOpts) *runtime.ContainerConfig {
 | 
						|
	cConfig := &runtime.ContainerConfig{
 | 
						|
		Metadata: &runtime.ContainerMetadata{
 | 
						|
			Name: name,
 | 
						|
		},
 | 
						|
		Image: &runtime.ImageSpec{Image: image},
 | 
						|
	}
 | 
						|
	for _, opt := range opts {
 | 
						|
		opt(cConfig)
 | 
						|
	}
 | 
						|
	return cConfig
 | 
						|
}
 | 
						|
 | 
						|
// CheckFunc is the function used to check a condition is true/false.
 | 
						|
type CheckFunc func() (bool, error)
 | 
						|
 | 
						|
// Eventually waits for f to return true, it checks every period, and
 | 
						|
// returns error if timeout exceeds. If f returns error, Eventually
 | 
						|
// will return the same error immediately.
 | 
						|
func Eventually(f CheckFunc, period, timeout time.Duration) error {
 | 
						|
	start := time.Now()
 | 
						|
	for {
 | 
						|
		done, err := f()
 | 
						|
		if done {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if time.Since(start) >= timeout {
 | 
						|
			return errors.New("timeout exceeded")
 | 
						|
		}
 | 
						|
		time.Sleep(period)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Consistently makes sure that f consistently returns true without
 | 
						|
// error before timeout exceeds. If f returns error, Consistently
 | 
						|
// will return the same error immediately.
 | 
						|
func Consistently(f CheckFunc, period, timeout time.Duration) error {
 | 
						|
	start := time.Now()
 | 
						|
	for {
 | 
						|
		ok, err := f()
 | 
						|
		if !ok {
 | 
						|
			return errors.New("get false")
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if time.Since(start) >= timeout {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		time.Sleep(period)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Randomize adds uuid after a string.
 | 
						|
func Randomize(str string) string {
 | 
						|
	return str + "-" + util.GenerateID()
 | 
						|
}
 | 
						|
 | 
						|
// KillProcess kills the process by name. pkill is used.
 | 
						|
func KillProcess(name string, signal syscall.Signal) error {
 | 
						|
	var command []string
 | 
						|
	if goruntime.GOOS == "windows" {
 | 
						|
		command = []string{"taskkill", "/IM", name, "/F"}
 | 
						|
	} else {
 | 
						|
		command = []string{"pkill", "-" + strconv.Itoa(int(signal)), "-x", fmt.Sprintf("^%s$", name)}
 | 
						|
	}
 | 
						|
 | 
						|
	output, err := exec.Command(command[0], command[1:]...).CombinedOutput()
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to kill %q - error: %v, output: %q", name, err, output)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// KillPid kills the process by pid. kill is used.
 | 
						|
func KillPid(pid int) error {
 | 
						|
	command := "kill"
 | 
						|
	if goruntime.GOOS == "windows" {
 | 
						|
		command = "tskill"
 | 
						|
	}
 | 
						|
	output, err := exec.Command(command, strconv.Itoa(pid)).CombinedOutput()
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to kill %d - error: %v, output: %q", pid, err, output)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// PidOf returns pid of a process by name.
 | 
						|
func PidOf(name string) (int, error) {
 | 
						|
	b, err := exec.Command("pidof", "-s", name).CombinedOutput()
 | 
						|
	output := strings.TrimSpace(string(b))
 | 
						|
	if err != nil {
 | 
						|
		if len(output) != 0 {
 | 
						|
			return 0, fmt.Errorf("failed to run pidof %q - error: %v, output: %q", name, err, output)
 | 
						|
		}
 | 
						|
		return 0, nil
 | 
						|
	}
 | 
						|
	return strconv.Atoi(output)
 | 
						|
}
 | 
						|
 | 
						|
// PidsOf returns pid(s) of a process by name
 | 
						|
func PidsOf(name string) ([]int, error) {
 | 
						|
	if len(name) == 0 {
 | 
						|
		return []int{}, fmt.Errorf("name is required")
 | 
						|
	}
 | 
						|
 | 
						|
	procDirFD, err := os.Open("/proc")
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to open /proc: %w", err)
 | 
						|
	}
 | 
						|
	defer procDirFD.Close()
 | 
						|
 | 
						|
	res := []int{}
 | 
						|
	for {
 | 
						|
		fileInfos, err := procDirFD.Readdir(100)
 | 
						|
		if err != nil {
 | 
						|
			if err == io.EOF {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			return nil, fmt.Errorf("failed to readdir: %w", err)
 | 
						|
		}
 | 
						|
 | 
						|
		for _, fileInfo := range fileInfos {
 | 
						|
			if !fileInfo.IsDir() {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			pid, err := strconv.Atoi(fileInfo.Name())
 | 
						|
			if err != nil {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			exePath, err := os.Readlink(filepath.Join("/proc", fileInfo.Name(), "exe"))
 | 
						|
			if err != nil {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			if strings.HasSuffix(exePath, name) {
 | 
						|
				res = append(res, pid)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return res, nil
 | 
						|
}
 | 
						|
 | 
						|
// PidEnvs returns the environ of pid in key-value pairs.
 | 
						|
func PidEnvs(pid int) (map[string]string, error) {
 | 
						|
	envPath := filepath.Join("/proc", strconv.Itoa(pid), "environ")
 | 
						|
 | 
						|
	b, err := os.ReadFile(envPath)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to read %s: %w", envPath, err)
 | 
						|
	}
 | 
						|
 | 
						|
	values := bytes.Split(b, []byte{0})
 | 
						|
	if len(values) == 0 {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	res := make(map[string]string)
 | 
						|
	for _, value := range values {
 | 
						|
		value = bytes.TrimSpace(value)
 | 
						|
		if len(value) == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		k, v, ok := strings.Cut(string(value), "=")
 | 
						|
		if ok {
 | 
						|
			res[k] = v
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return res, nil
 | 
						|
}
 | 
						|
 | 
						|
// RawRuntimeClient returns a raw grpc runtime service client.
 | 
						|
func RawRuntimeClient() (runtime.RuntimeServiceClient, error) {
 | 
						|
	addr, dialer, err := dialer.GetAddressAndDialer(*criEndpoint)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to get dialer: %w", err)
 | 
						|
	}
 | 
						|
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 | 
						|
	defer cancel()
 | 
						|
	conn, err := grpc.DialContext(ctx, addr,
 | 
						|
		grpc.WithTransportCredentials(insecure.NewCredentials()),
 | 
						|
		grpc.WithContextDialer(dialer),
 | 
						|
	)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to connect cri endpoint: %w", err)
 | 
						|
	}
 | 
						|
	return runtime.NewRuntimeServiceClient(conn), nil
 | 
						|
}
 | 
						|
 | 
						|
// CRIConfig gets current cri config from containerd.
 | 
						|
func CRIConfig() (*criconfig.Config, error) {
 | 
						|
	client, err := RawRuntimeClient()
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to get raw runtime client: %w", err)
 | 
						|
	}
 | 
						|
	resp, err := client.Status(context.Background(), &runtime.StatusRequest{Verbose: true})
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to get status: %w", err)
 | 
						|
	}
 | 
						|
	config := &criconfig.Config{}
 | 
						|
	if err := json.Unmarshal([]byte(resp.Info["config"]), config); err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to unmarshal config: %w", err)
 | 
						|
	}
 | 
						|
	return config, nil
 | 
						|
}
 | 
						|
 | 
						|
// SandboxInfo gets sandbox info.
 | 
						|
func SandboxInfo(id string) (*runtime.PodSandboxStatus, *server.SandboxInfo, error) {
 | 
						|
	client, err := RawRuntimeClient()
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("failed to get raw runtime client: %w", err)
 | 
						|
	}
 | 
						|
	resp, err := client.PodSandboxStatus(context.Background(), &runtime.PodSandboxStatusRequest{
 | 
						|
		PodSandboxId: id,
 | 
						|
		Verbose:      true,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("failed to get sandbox status: %w", err)
 | 
						|
	}
 | 
						|
	status := resp.GetStatus()
 | 
						|
	var info server.SandboxInfo
 | 
						|
	if err := json.Unmarshal([]byte(resp.GetInfo()["info"]), &info); err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("failed to unmarshal sandbox info: %w", err)
 | 
						|
	}
 | 
						|
	return status, &info, nil
 | 
						|
}
 | 
						|
 | 
						|
func RestartContainerd(t *testing.T, signal syscall.Signal) {
 | 
						|
	require.NoError(t, KillProcess(*containerdBin, signal))
 | 
						|
 | 
						|
	// Use assert so that the 3rd wait always runs, this makes sure
 | 
						|
	// containerd is running before this function returns.
 | 
						|
	assert.NoError(t, Eventually(func() (bool, error) {
 | 
						|
		pid, err := PidOf(*containerdBin)
 | 
						|
		if err != nil {
 | 
						|
			return false, err
 | 
						|
		}
 | 
						|
		return pid == 0, nil
 | 
						|
	}, time.Second, 30*time.Second), "wait for containerd to be killed")
 | 
						|
 | 
						|
	require.NoError(t, Eventually(func() (bool, error) {
 | 
						|
		return ConnectDaemons() == nil, nil
 | 
						|
	}, time.Second, 30*time.Second), "wait for containerd to be restarted")
 | 
						|
}
 | 
						|
 | 
						|
// EnsureImageExists pulls the given image, ensures that no error was encountered
 | 
						|
// while pulling it.
 | 
						|
func EnsureImageExists(t *testing.T, imageName string) string {
 | 
						|
	img, err := imageService.ImageStatus(&runtime.ImageSpec{Image: imageName})
 | 
						|
	require.NoError(t, err)
 | 
						|
	if img != nil {
 | 
						|
		t.Logf("Image %q already exists, not pulling.", imageName)
 | 
						|
		return img.Id
 | 
						|
	}
 | 
						|
 | 
						|
	t.Logf("Pull test image %q", imageName)
 | 
						|
	imgID, err := imageService.PullImage(&runtime.ImageSpec{Image: imageName}, nil, nil)
 | 
						|
	require.NoError(t, err)
 | 
						|
 | 
						|
	return imgID
 | 
						|
}
 | 
						|
 | 
						|
func GetContainer(id string) (containers.Container, error) {
 | 
						|
	return containerdClient.ContainerService().Get(context.Background(), id)
 | 
						|
}
 |