mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-10-22 04:18:53 +00:00
Add initial support for opentracing by using the `jaeger` package. Since opentracing uses the `context` package, add a `context.Context` as the first parameter to all the functions that we might want to trace. Trace "spans" (trace points) are then added by extracting the trace details from the specified context parameter. Notes: - Although the tracer is created in `main()`, the "root span" (aka the first trace point) is not added until `beforeSubcommands()`. This is by design and is a compromise: by delaying the creation of the root span, the spans become much more readable since using the web-based JaegerUI, you will see traces like this: ``` kata-runtime: kata-runtime create ------------ ------------------- ^ ^ | | Trace name First span name (which clearly shows the CLI command that was run) ``` Creating the span earlier means it is necessary to expand 'n' spans in the UI before you get to see the name of the CLI command that was run. In adding support, this became very tedious, hence my design decision to defer the creation of the root span until after signal handling has been setup and after CLI options have been parsed, but still very early in the code path. - At this stage, the tracing stops at the `virtcontainers` call boundary. - Tracing is "always on" as there doesn't appear to be a way to toggle it. However, its resolves to a "nop" unless the tracer can talk to a jaeger agent. Note that this commit required a bit of rework to `beforeSubcommands()` to reduce the cyclomatic complexity. Fixes #557. Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
1126 lines
26 KiB
Go
1126 lines
26 KiB
Go
// Copyright (c) 2017 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/dlespiau/covertool/pkg/cover"
|
|
vc "github.com/kata-containers/runtime/virtcontainers"
|
|
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
|
"github.com/kata-containers/runtime/virtcontainers/pkg/vcmock"
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/stretchr/testify/assert"
|
|
jaeger "github.com/uber/jaeger-client-go"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
const (
|
|
testDisabledNeedRoot = "Test disabled as requires root user"
|
|
testDisabledNeedNonRoot = "Test disabled as requires non-root user"
|
|
testDirMode = os.FileMode(0750)
|
|
testFileMode = os.FileMode(0640)
|
|
testExeFileMode = os.FileMode(0750)
|
|
|
|
// small docker image used to create root filesystems from
|
|
testDockerImage = "busybox"
|
|
|
|
testSandboxID = "99999999-9999-9999-99999999999999999"
|
|
testContainerID = "1"
|
|
testBundle = "bundle"
|
|
)
|
|
|
|
var (
|
|
// package variables set by calling TestMain()
|
|
testDir = ""
|
|
testBundleDir = ""
|
|
)
|
|
|
|
// testingImpl is a concrete mock RVC implementation used for testing
|
|
var testingImpl = &vcmock.VCMock{}
|
|
|
|
func init() {
|
|
if version == "" {
|
|
panic("ERROR: invalid build: version not set")
|
|
}
|
|
|
|
if commit == "" {
|
|
panic("ERROR: invalid build: commit not set")
|
|
}
|
|
|
|
if defaultSysConfRuntimeConfiguration == "" {
|
|
panic("ERROR: invalid build: defaultSysConfRuntimeConfiguration not set")
|
|
}
|
|
|
|
if defaultRuntimeConfiguration == "" {
|
|
panic("ERROR: invalid build: defaultRuntimeConfiguration not set")
|
|
}
|
|
|
|
fmt.Printf("INFO: running as actual user %v (effective %v), actual group %v (effective %v)\n",
|
|
os.Getuid(), os.Geteuid(), os.Getgid(), os.Getegid())
|
|
|
|
fmt.Printf("INFO: switching to fake virtcontainers implementation for testing\n")
|
|
vci = testingImpl
|
|
|
|
var err error
|
|
|
|
fmt.Printf("INFO: creating test directory\n")
|
|
testDir, err = ioutil.TempDir("", fmt.Sprintf("%s-", name))
|
|
if err != nil {
|
|
panic(fmt.Sprintf("ERROR: failed to create test directory: %v", err))
|
|
}
|
|
|
|
fmt.Printf("INFO: test directory is %v\n", testDir)
|
|
|
|
fmt.Printf("INFO: ensuring docker is running\n")
|
|
output, err := runCommandFull([]string{"docker", "version"}, true)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("ERROR: docker daemon is not installed, not running, or not accessible to current user: %v (error %v)",
|
|
output, err))
|
|
}
|
|
|
|
// Do this now to avoid hitting the test timeout value due to
|
|
// slow network response.
|
|
fmt.Printf("INFO: ensuring required docker image (%v) is available\n", testDockerImage)
|
|
|
|
// Only hit the network if the image doesn't exist locally
|
|
_, err = runCommand([]string{"docker", "inspect", "--type=image", testDockerImage})
|
|
if err == nil {
|
|
fmt.Printf("INFO: docker image %v already exists locally\n", testDockerImage)
|
|
} else {
|
|
_, err = runCommand([]string{"docker", "pull", testDockerImage})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
testBundleDir = filepath.Join(testDir, testBundle)
|
|
err = os.MkdirAll(testBundleDir, testDirMode)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("ERROR: failed to create bundle directory %v: %v", testBundleDir, err))
|
|
}
|
|
|
|
fmt.Printf("INFO: creating OCI bundle in %v for tests to use\n", testBundleDir)
|
|
err = realMakeOCIBundle(testBundleDir)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("ERROR: failed to create OCI bundle: %v", err))
|
|
}
|
|
}
|
|
|
|
// resetCLIGlobals undoes the effects of setCLIGlobals(), restoring the original values
|
|
func resetCLIGlobals() {
|
|
cli.AppHelpTemplate = savedCLIAppHelpTemplate
|
|
cli.VersionPrinter = savedCLIVersionPrinter
|
|
cli.ErrWriter = savedCLIErrWriter
|
|
}
|
|
|
|
func runUnitTests(m *testing.M) {
|
|
ret := m.Run()
|
|
|
|
os.RemoveAll(testDir)
|
|
|
|
os.Exit(ret)
|
|
}
|
|
|
|
// Read fail that should contain a CompatOCISpec and
|
|
// return its JSON representation on success
|
|
func readOCIConfigJSON(configFile string) (string, error) {
|
|
bundlePath := filepath.Dir(configFile)
|
|
ociSpec, err := oci.ParseConfigJSON(bundlePath)
|
|
if err != nil {
|
|
return "", nil
|
|
}
|
|
ociSpecJSON, err := json.Marshal(ociSpec)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(ociSpecJSON), err
|
|
}
|
|
|
|
// TestMain is the common main function used by ALL the test functions
|
|
// for this package.
|
|
func TestMain(m *testing.M) {
|
|
// Parse the command line using the stdlib flag package so the flags defined
|
|
// in the testing package get populated.
|
|
cover.ParseAndStripTestFlags()
|
|
|
|
// Make sure we have the opportunity to flush the coverage report to disk when
|
|
// terminating the process.
|
|
atexit(cover.FlushProfiles)
|
|
|
|
// If the test binary name is kata-runtime.coverage, we've are being asked to
|
|
// run the coverage-instrumented kata-runtime.
|
|
if path.Base(os.Args[0]) == name+".coverage" ||
|
|
path.Base(os.Args[0]) == name {
|
|
main()
|
|
exit(0)
|
|
}
|
|
|
|
runUnitTests(m)
|
|
}
|
|
|
|
func createEmptyFile(path string) (err error) {
|
|
return ioutil.WriteFile(path, []byte(""), testFileMode)
|
|
}
|
|
|
|
func grep(pattern, file string) error {
|
|
if file == "" {
|
|
return errors.New("need file")
|
|
}
|
|
|
|
bytes, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
re := regexp.MustCompile(pattern)
|
|
matches := re.FindAllStringSubmatch(string(bytes), -1)
|
|
|
|
if matches == nil {
|
|
return fmt.Errorf("pattern %q not found in file %q", pattern, file)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// newTestHypervisorConfig creaets a new virtcontainers
|
|
// HypervisorConfig, ensuring that the required resources are also
|
|
// created.
|
|
//
|
|
// Note: no parameter validation in case caller wishes to create an invalid
|
|
// object.
|
|
func newTestHypervisorConfig(dir string, create bool) (vc.HypervisorConfig, error) {
|
|
kernelPath := path.Join(dir, "kernel")
|
|
imagePath := path.Join(dir, "image")
|
|
hypervisorPath := path.Join(dir, "hypervisor")
|
|
|
|
if create {
|
|
for _, file := range []string{kernelPath, imagePath, hypervisorPath} {
|
|
err := createEmptyFile(file)
|
|
if err != nil {
|
|
return vc.HypervisorConfig{}, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return vc.HypervisorConfig{
|
|
KernelPath: kernelPath,
|
|
ImagePath: imagePath,
|
|
HypervisorPath: hypervisorPath,
|
|
HypervisorMachineType: "pc-lite",
|
|
}, nil
|
|
}
|
|
|
|
// newTestRuntimeConfig creates a new RuntimeConfig
|
|
func newTestRuntimeConfig(dir, consolePath string, create bool) (oci.RuntimeConfig, error) {
|
|
if dir == "" {
|
|
return oci.RuntimeConfig{}, errors.New("BUG: need directory")
|
|
}
|
|
|
|
hypervisorConfig, err := newTestHypervisorConfig(dir, create)
|
|
if err != nil {
|
|
return oci.RuntimeConfig{}, err
|
|
}
|
|
|
|
return oci.RuntimeConfig{
|
|
HypervisorType: vc.QemuHypervisor,
|
|
HypervisorConfig: hypervisorConfig,
|
|
AgentType: vc.KataContainersAgent,
|
|
ProxyType: vc.CCProxyType,
|
|
ShimType: vc.CCShimType,
|
|
Console: consolePath,
|
|
}, nil
|
|
}
|
|
|
|
// createOCIConfig creates an OCI configuration (spec) file in
|
|
// the bundle directory specified (which must exist).
|
|
func createOCIConfig(bundleDir string) error {
|
|
if bundleDir == "" {
|
|
return errors.New("BUG: Need bundle directory")
|
|
}
|
|
|
|
if !fileExists(bundleDir) {
|
|
return fmt.Errorf("BUG: Bundle directory %s does not exist", bundleDir)
|
|
}
|
|
|
|
var configCmd string
|
|
|
|
// Search for a suitable version of runc to use to generate
|
|
// the OCI config file.
|
|
for _, cmd := range []string{"docker-runc", "runc"} {
|
|
fullPath, err := exec.LookPath(cmd)
|
|
if err == nil {
|
|
configCmd = fullPath
|
|
break
|
|
}
|
|
}
|
|
|
|
if configCmd == "" {
|
|
return errors.New("Cannot find command to generate OCI config file")
|
|
}
|
|
|
|
_, err := runCommand([]string{configCmd, "spec", "--bundle", bundleDir})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
specFile := filepath.Join(bundleDir, specConfig)
|
|
if !fileExists(specFile) {
|
|
return fmt.Errorf("generated OCI config file does not exist: %v", specFile)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createRootfs creates a minimal root filesystem below the specified
|
|
// directory.
|
|
func createRootfs(dir string) error {
|
|
err := os.MkdirAll(dir, testDirMode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
container, err := runCommand([]string{"docker", "create", testDockerImage})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd1 := exec.Command("docker", "export", container)
|
|
cmd2 := exec.Command("tar", "-C", dir, "-xvf", "-")
|
|
|
|
cmd1Stdout, err := cmd1.StdoutPipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd2.Stdin = cmd1Stdout
|
|
|
|
err = cmd2.Start()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = cmd1.Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = cmd2.Wait()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Clean up
|
|
_, err = runCommand([]string{"docker", "rm", container})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// realMakeOCIBundle will create an OCI bundle (including the "config.json"
|
|
// config file) in the directory specified (which must already exist).
|
|
//
|
|
// XXX: Note that tests should *NOT* call this function - they should
|
|
// XXX: instead call makeOCIBundle().
|
|
func realMakeOCIBundle(bundleDir string) error {
|
|
if bundleDir == "" {
|
|
return errors.New("BUG: Need bundle directory")
|
|
}
|
|
|
|
if !fileExists(bundleDir) {
|
|
return fmt.Errorf("BUG: Bundle directory %v does not exist", bundleDir)
|
|
}
|
|
|
|
err := createOCIConfig(bundleDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Note the unusual parameter (a directory, not the config
|
|
// file to parse!)
|
|
spec, err := oci.ParseConfigJSON(bundleDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Determine the rootfs directory name the OCI config refers to
|
|
ociRootPath := spec.Root.Path
|
|
|
|
rootfsDir := filepath.Join(bundleDir, ociRootPath)
|
|
|
|
if strings.HasPrefix(ociRootPath, "/") {
|
|
return fmt.Errorf("Cannot handle absolute rootfs as bundle must be unique to each test")
|
|
}
|
|
|
|
err = createRootfs(rootfsDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Create an OCI bundle in the specified directory.
|
|
//
|
|
// Note that the directory will be created, but it's parent is expected to exist.
|
|
//
|
|
// This function works by copying the already-created test bundle. Ideally,
|
|
// the bundle would be recreated for each test, but createRootfs() uses
|
|
// docker which on some systems is too slow, resulting in the tests timing
|
|
// out.
|
|
func makeOCIBundle(bundleDir string) error {
|
|
from := testBundleDir
|
|
to := bundleDir
|
|
|
|
// only the basename of bundleDir needs to exist as bundleDir
|
|
// will get created by cp(1).
|
|
base := filepath.Dir(bundleDir)
|
|
|
|
for _, dir := range []string{from, base} {
|
|
if !fileExists(dir) {
|
|
return fmt.Errorf("BUG: directory %v should exist", dir)
|
|
}
|
|
}
|
|
|
|
output, err := runCommandFull([]string{"cp", "-a", from, to}, true)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to copy test OCI bundle from %v to %v: %v (output: %v)", from, to, err, output)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// readOCIConfig returns an OCI spec.
|
|
func readOCIConfigFile(configPath string) (oci.CompatOCISpec, error) {
|
|
if configPath == "" {
|
|
return oci.CompatOCISpec{}, errors.New("BUG: need config file path")
|
|
}
|
|
|
|
data, err := ioutil.ReadFile(configPath)
|
|
if err != nil {
|
|
return oci.CompatOCISpec{}, err
|
|
}
|
|
|
|
var ociSpec oci.CompatOCISpec
|
|
if err := json.Unmarshal(data, &ociSpec); err != nil {
|
|
return oci.CompatOCISpec{}, err
|
|
}
|
|
caps, err := oci.ContainerCapabilities(ociSpec)
|
|
if err != nil {
|
|
return oci.CompatOCISpec{}, err
|
|
}
|
|
ociSpec.Process.Capabilities = caps
|
|
return ociSpec, nil
|
|
}
|
|
|
|
func writeOCIConfigFile(spec oci.CompatOCISpec, configPath string) error {
|
|
if configPath == "" {
|
|
return errors.New("BUG: need config file path")
|
|
}
|
|
|
|
bytes, err := json.MarshalIndent(spec, "", "\t")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ioutil.WriteFile(configPath, bytes, testFileMode)
|
|
}
|
|
|
|
func newSingleContainerStatus(containerID string, containerState vc.State, annotations map[string]string) vc.ContainerStatus {
|
|
return vc.ContainerStatus{
|
|
ID: containerID,
|
|
State: containerState,
|
|
Annotations: annotations,
|
|
}
|
|
}
|
|
|
|
func execCLICommandFunc(assertHandler *assert.Assertions, cliCommand cli.Command, set *flag.FlagSet, expectedErr bool) {
|
|
ctx := createCLIContext(set)
|
|
ctx.App.Name = "foo"
|
|
|
|
fn, ok := cliCommand.Action.(func(context *cli.Context) error)
|
|
assertHandler.True(ok)
|
|
|
|
err := fn(ctx)
|
|
|
|
if expectedErr {
|
|
assertHandler.Error(err)
|
|
} else {
|
|
assertHandler.Nil(err)
|
|
}
|
|
}
|
|
|
|
func createCLIContextWithApp(flagSet *flag.FlagSet, app *cli.App) *cli.Context {
|
|
ctx := cli.NewContext(app, flagSet, nil)
|
|
|
|
// create the map if required
|
|
if ctx.App.Metadata == nil {
|
|
ctx.App.Metadata = map[string]interface{}{}
|
|
}
|
|
|
|
// add standard entries
|
|
ctx.App.Metadata["context"] = context.Background()
|
|
ctx.App.Metadata["tracer"] = &jaeger.Tracer{}
|
|
|
|
return ctx
|
|
}
|
|
|
|
func createCLIContext(flagset *flag.FlagSet) *cli.Context {
|
|
return createCLIContextWithApp(flagset, cli.NewApp())
|
|
}
|
|
|
|
func TestMakeOCIBundle(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
bundleDir := filepath.Join(tmpdir, "bundle")
|
|
|
|
err = makeOCIBundle(bundleDir)
|
|
assert.NoError(err)
|
|
|
|
specFile := filepath.Join(bundleDir, specConfig)
|
|
assert.True(fileExists(specFile))
|
|
}
|
|
|
|
func TestCreateOCIConfig(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
bundleDir := filepath.Join(tmpdir, "bundle")
|
|
|
|
err = createOCIConfig(bundleDir)
|
|
// ENOENT
|
|
assert.Error(err)
|
|
|
|
err = os.MkdirAll(bundleDir, testDirMode)
|
|
assert.NoError(err)
|
|
|
|
err = createOCIConfig(bundleDir)
|
|
assert.NoError(err)
|
|
|
|
specFile := filepath.Join(bundleDir, specConfig)
|
|
assert.True(fileExists(specFile))
|
|
}
|
|
|
|
func TestCreateRootfs(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
rootfsDir := filepath.Join(tmpdir, "rootfs")
|
|
assert.False(fileExists(rootfsDir))
|
|
|
|
err = createRootfs(rootfsDir)
|
|
assert.NoError(err)
|
|
|
|
// non-comprehensive list of expected directories
|
|
expectedDirs := []string{"bin", "dev", "etc", "usr", "var"}
|
|
|
|
assert.True(fileExists(rootfsDir))
|
|
|
|
for _, dir := range expectedDirs {
|
|
dirPath := filepath.Join(rootfsDir, dir)
|
|
assert.True(fileExists(dirPath))
|
|
}
|
|
}
|
|
|
|
func TestMainUserWantsUsage(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
type testData struct {
|
|
arguments []string
|
|
expectTrue bool
|
|
}
|
|
|
|
data := []testData{
|
|
{[]string{}, true},
|
|
{[]string{"help"}, true},
|
|
{[]string{"version"}, true},
|
|
{[]string{"sub-command", "-h"}, true},
|
|
{[]string{"sub-command", "--help"}, true},
|
|
|
|
{[]string{""}, false},
|
|
{[]string{"sub-command", "--foo"}, false},
|
|
{[]string{"kata-check"}, false},
|
|
{[]string{"haaaalp"}, false},
|
|
{[]string{"wibble"}, false},
|
|
{[]string{"versioned"}, false},
|
|
}
|
|
|
|
for i, d := range data {
|
|
set := flag.NewFlagSet("", 0)
|
|
set.Parse(d.arguments)
|
|
|
|
ctx := createCLIContext(set)
|
|
result := userWantsUsage(ctx)
|
|
|
|
if d.expectTrue {
|
|
assert.True(result, "test %d (%+v)", i, d)
|
|
} else {
|
|
assert.False(result, "test %d (%+v)", i, d)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMainBeforeSubCommands(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
type testData struct {
|
|
arguments []string
|
|
expectError bool
|
|
}
|
|
|
|
data := []testData{
|
|
{[]string{}, false},
|
|
{[]string{"help"}, false},
|
|
{[]string{"version"}, false},
|
|
{[]string{"sub-command", "-h"}, false},
|
|
{[]string{"sub-command", "--help"}, false},
|
|
{[]string{"kata-check"}, false},
|
|
}
|
|
|
|
for i, d := range data {
|
|
set := flag.NewFlagSet("", 0)
|
|
set.Parse(d.arguments)
|
|
|
|
ctx := createCLIContext(set)
|
|
err := beforeSubcommands(ctx)
|
|
|
|
if d.expectError {
|
|
assert.Errorf(err, "test %d (%+v)", i, d)
|
|
} else {
|
|
assert.NoError(err, "test %d (%+v)", i, d)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMainBeforeSubCommandsInvalidLogFile(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
logFile := filepath.Join(tmpdir, "log")
|
|
|
|
// create the file as the wrong type to force a failure
|
|
err = os.MkdirAll(logFile, testDirMode)
|
|
assert.NoError(err)
|
|
|
|
set := flag.NewFlagSet("", 0)
|
|
set.String("log", logFile, "")
|
|
set.Parse([]string{"create"})
|
|
|
|
ctx := createCLIContext(set)
|
|
|
|
err = beforeSubcommands(ctx)
|
|
assert.Error(err)
|
|
}
|
|
|
|
func TestMainBeforeSubCommandsInvalidLogFormat(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
logFile := filepath.Join(tmpdir, "log")
|
|
|
|
set := flag.NewFlagSet("", 0)
|
|
set.Bool("debug", true, "")
|
|
set.String("log", logFile, "")
|
|
set.String("log-format", "captain-barnacles", "")
|
|
set.Parse([]string{"create"})
|
|
|
|
logOut := kataLog.Logger.Out
|
|
kataLog.Logger.Out = nil
|
|
|
|
defer func() {
|
|
kataLog.Logger.Out = logOut
|
|
}()
|
|
|
|
ctx := createCLIContext(set)
|
|
|
|
err = beforeSubcommands(ctx)
|
|
assert.Error(err)
|
|
assert.NotNil(kataLog.Logger.Out)
|
|
}
|
|
|
|
func TestMainBeforeSubCommandsLoadConfigurationFail(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
logFile := filepath.Join(tmpdir, "log")
|
|
configFile := filepath.Join(tmpdir, "config")
|
|
|
|
for _, logFormat := range []string{"json", "text"} {
|
|
set := flag.NewFlagSet("", 0)
|
|
set.Bool("debug", true, "")
|
|
set.String("log", logFile, "")
|
|
set.String("log-format", logFormat, "")
|
|
set.String("kata-config", configFile, "")
|
|
set.Parse([]string{"kata-env"})
|
|
|
|
ctx := createCLIContext(set)
|
|
|
|
savedExitFunc := exitFunc
|
|
|
|
exitStatus := 0
|
|
exitFunc = func(status int) { exitStatus = status }
|
|
|
|
defer func() {
|
|
exitFunc = savedExitFunc
|
|
}()
|
|
|
|
// calls fatal() so no return
|
|
_ = beforeSubcommands(ctx)
|
|
assert.NotEqual(exitStatus, 0)
|
|
}
|
|
}
|
|
|
|
func TestMainBeforeSubCommandsShowCCConfigPaths(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
set := flag.NewFlagSet("", 0)
|
|
set.Bool("kata-show-default-config-paths", true, "")
|
|
|
|
ctx := createCLIContext(set)
|
|
|
|
savedExitFunc := exitFunc
|
|
|
|
exitStatus := 99
|
|
exitFunc = func(status int) { exitStatus = status }
|
|
|
|
defer func() {
|
|
exitFunc = savedExitFunc
|
|
}()
|
|
|
|
savedOutputFile := defaultOutputFile
|
|
|
|
defer func() {
|
|
resetCLIGlobals()
|
|
defaultOutputFile = savedOutputFile
|
|
}()
|
|
|
|
output := filepath.Join(tmpdir, "output")
|
|
f, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_SYNC, testFileMode)
|
|
assert.NoError(err)
|
|
defer f.Close()
|
|
|
|
defaultOutputFile = f
|
|
|
|
setCLIGlobals()
|
|
|
|
_ = beforeSubcommands(ctx)
|
|
assert.Equal(exitStatus, 0)
|
|
|
|
text, err := getFileContents(output)
|
|
assert.NoError(err)
|
|
|
|
lines := strings.Split(text, "\n")
|
|
|
|
// Remove last line if empty
|
|
length := len(lines)
|
|
last := lines[length-1]
|
|
if last == "" {
|
|
lines = lines[:length-1]
|
|
}
|
|
|
|
assert.Equal(len(lines), 2)
|
|
|
|
for i, line := range lines {
|
|
switch i {
|
|
case 0:
|
|
assert.Equal(line, defaultSysConfRuntimeConfiguration)
|
|
case 1:
|
|
assert.Equal(line, defaultRuntimeConfiguration)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMainFatal(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
var exitStatus int
|
|
savedExitFunc := exitFunc
|
|
|
|
exitFunc = func(status int) { exitStatus = status }
|
|
|
|
savedErrorFile := defaultErrorFile
|
|
|
|
output := filepath.Join(tmpdir, "output")
|
|
f, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_SYNC, testFileMode)
|
|
assert.NoError(err)
|
|
defaultErrorFile = f
|
|
|
|
defer func() {
|
|
f.Close()
|
|
defaultErrorFile = savedErrorFile
|
|
exitFunc = savedExitFunc
|
|
}()
|
|
|
|
exitError := errors.New("hello world")
|
|
|
|
fatal(exitError)
|
|
assert.Equal(exitStatus, 1)
|
|
|
|
text, err := getFileContents(output)
|
|
assert.NoError(err)
|
|
|
|
trimmed := strings.TrimSpace(text)
|
|
assert.Equal(exitError.Error(), trimmed)
|
|
}
|
|
|
|
func testVersionString(assert *assert.Assertions, versionString, expectedVersion, expectedCommit, expectedOCIVersion string) {
|
|
foundVersion := false
|
|
foundCommit := false
|
|
foundOCIVersion := false
|
|
|
|
versionRE := regexp.MustCompile(fmt.Sprintf(`%s\s*:\s*%v`, name, expectedVersion))
|
|
commitRE := regexp.MustCompile(fmt.Sprintf(`%s\s*:\s*%v`, "commit", expectedCommit))
|
|
|
|
ociRE := regexp.MustCompile(fmt.Sprintf(`%s\s*:\s*%v`, "OCI specs", expectedOCIVersion))
|
|
|
|
lines := strings.Split(versionString, "\n")
|
|
assert.True(len(lines) > 0)
|
|
|
|
for _, line := range lines {
|
|
vMatches := versionRE.FindAllStringSubmatch(line, -1)
|
|
if vMatches != nil {
|
|
foundVersion = true
|
|
}
|
|
|
|
cMatches := commitRE.FindAllStringSubmatch(line, -1)
|
|
if cMatches != nil {
|
|
foundCommit = true
|
|
}
|
|
|
|
oMatches := ociRE.FindAllStringSubmatch(line, -1)
|
|
if oMatches != nil {
|
|
foundOCIVersion = true
|
|
}
|
|
}
|
|
|
|
args := fmt.Sprintf("versionString: %q, expectedVersion: %q, expectedCommit: %v, expectedOCIVersion: %v\n",
|
|
versionString, expectedVersion, expectedCommit, expectedOCIVersion)
|
|
|
|
assert.True(foundVersion, args)
|
|
assert.True(foundCommit, args)
|
|
assert.True(foundOCIVersion, args)
|
|
}
|
|
|
|
func TestMainMakeVersionString(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
v := makeVersionString()
|
|
|
|
testVersionString(assert, v, version, commit, specs.Version)
|
|
}
|
|
|
|
func TestMainMakeVersionStringNoVersion(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
savedVersion := version
|
|
version = ""
|
|
|
|
defer func() {
|
|
version = savedVersion
|
|
}()
|
|
|
|
v := makeVersionString()
|
|
|
|
testVersionString(assert, v, unknown, commit, specs.Version)
|
|
}
|
|
|
|
func TestMainMakeVersionStringNoCommit(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
savedCommit := commit
|
|
commit = ""
|
|
|
|
defer func() {
|
|
commit = savedCommit
|
|
}()
|
|
|
|
v := makeVersionString()
|
|
|
|
testVersionString(assert, v, version, unknown, specs.Version)
|
|
}
|
|
|
|
func TestMainMakeVersionStringNoOCIVersion(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
savedVersion := specs.Version
|
|
specs.Version = ""
|
|
|
|
defer func() {
|
|
specs.Version = savedVersion
|
|
}()
|
|
|
|
v := makeVersionString()
|
|
|
|
testVersionString(assert, v, version, commit, unknown)
|
|
}
|
|
|
|
func TestMainCreateRuntimeApp(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
savedBefore := runtimeBeforeSubcommands
|
|
savedOutputFile := defaultOutputFile
|
|
|
|
// disable
|
|
runtimeBeforeSubcommands = nil
|
|
|
|
devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0640)
|
|
assert.NoError(err)
|
|
defer devNull.Close()
|
|
|
|
defaultOutputFile = devNull
|
|
|
|
setCLIGlobals()
|
|
|
|
defer func() {
|
|
resetCLIGlobals()
|
|
runtimeBeforeSubcommands = savedBefore
|
|
defaultOutputFile = savedOutputFile
|
|
}()
|
|
|
|
args := []string{name}
|
|
|
|
err = createRuntimeApp(context.Background(), args)
|
|
assert.NoError(err, "%v", args)
|
|
}
|
|
|
|
func TestMainCreateRuntimeAppInvalidSubCommand(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
exitStatus := 0
|
|
|
|
savedBefore := runtimeBeforeSubcommands
|
|
savedExitFunc := exitFunc
|
|
|
|
exitFunc = func(status int) { exitStatus = status }
|
|
|
|
// disable
|
|
runtimeBeforeSubcommands = nil
|
|
|
|
defer func() {
|
|
runtimeBeforeSubcommands = savedBefore
|
|
exitFunc = savedExitFunc
|
|
}()
|
|
|
|
// calls fatal() so no return
|
|
_ = createRuntimeApp(context.Background(), []string{name, "i-am-an-invalid-sub-command"})
|
|
|
|
assert.NotEqual(exitStatus, 0)
|
|
}
|
|
|
|
func TestMainCreateRuntime(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
const cmd = "foo"
|
|
const msg = "moo FAILURE"
|
|
|
|
resetCLIGlobals()
|
|
|
|
exitStatus := 0
|
|
|
|
savedOSArgs := os.Args
|
|
savedExitFunc := exitFunc
|
|
savedBefore := runtimeBeforeSubcommands
|
|
savedCommands := runtimeCommands
|
|
|
|
os.Args = []string{name, cmd}
|
|
exitFunc = func(status int) { exitStatus = status }
|
|
|
|
// disable
|
|
runtimeBeforeSubcommands = nil
|
|
|
|
// override sub-commands
|
|
runtimeCommands = []cli.Command{
|
|
{
|
|
Name: cmd,
|
|
Action: func(context *cli.Context) error {
|
|
return errors.New(msg)
|
|
},
|
|
},
|
|
}
|
|
|
|
defer func() {
|
|
os.Args = savedOSArgs
|
|
exitFunc = savedExitFunc
|
|
runtimeBeforeSubcommands = savedBefore
|
|
runtimeCommands = savedCommands
|
|
}()
|
|
|
|
assert.Equal(exitStatus, 0)
|
|
createRuntime(context.Background())
|
|
assert.NotEqual(exitStatus, 0)
|
|
}
|
|
|
|
func TestMainVersionPrinter(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir("", "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
savedOutputFile := defaultOutputFile
|
|
|
|
defer func() {
|
|
resetCLIGlobals()
|
|
defaultOutputFile = savedOutputFile
|
|
}()
|
|
|
|
output := filepath.Join(tmpdir, "output")
|
|
f, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_SYNC, testFileMode)
|
|
assert.NoError(err)
|
|
defer f.Close()
|
|
|
|
defaultOutputFile = f
|
|
|
|
setCLIGlobals()
|
|
|
|
err = createRuntimeApp(context.Background(), []string{name, "--version"})
|
|
assert.NoError(err)
|
|
|
|
err = grep(fmt.Sprintf(`%s\s*:\s*%s`, name, version), output)
|
|
assert.NoError(err)
|
|
}
|
|
|
|
func TestMainFatalWriter(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
const cmd = "foo"
|
|
const msg = "moo FAILURE"
|
|
|
|
// create buffer to save logger output
|
|
buf := &bytes.Buffer{}
|
|
|
|
savedBefore := runtimeBeforeSubcommands
|
|
savedLogOutput := kataLog.Logger.Out
|
|
savedCLIExiter := cli.OsExiter
|
|
savedCommands := runtimeCommands
|
|
|
|
// disable
|
|
runtimeBeforeSubcommands = nil
|
|
|
|
// save all output
|
|
kataLog.Logger.Out = buf
|
|
|
|
cli.OsExiter = func(status int) {}
|
|
|
|
// override sub-commands
|
|
runtimeCommands = []cli.Command{
|
|
{
|
|
Name: cmd,
|
|
Action: func(context *cli.Context) error {
|
|
return cli.NewExitError(msg, 42)
|
|
},
|
|
},
|
|
}
|
|
|
|
defer func() {
|
|
runtimeBeforeSubcommands = savedBefore
|
|
kataLog.Logger.Out = savedLogOutput
|
|
cli.OsExiter = savedCLIExiter
|
|
runtimeCommands = savedCommands
|
|
}()
|
|
|
|
setCLIGlobals()
|
|
|
|
err := createRuntimeApp(context.Background(), []string{name, cmd})
|
|
assert.Error(err)
|
|
|
|
re := regexp.MustCompile(
|
|
fmt.Sprintf(`\blevel\b.*\berror\b.*\b%s\b`, msg))
|
|
matches := re.FindAllStringSubmatch(buf.String(), -1)
|
|
assert.NotEmpty(matches)
|
|
}
|
|
|
|
func TestMainSetCLIGlobals(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
defer resetCLIGlobals()
|
|
|
|
cli.AppHelpTemplate = ""
|
|
cli.VersionPrinter = nil
|
|
cli.ErrWriter = nil
|
|
|
|
setCLIGlobals()
|
|
|
|
assert.NotEqual(cli.AppHelpTemplate, "")
|
|
assert.NotNil(cli.VersionPrinter)
|
|
assert.NotNil(cli.ErrWriter)
|
|
}
|
|
|
|
func TestMainResetCLIGlobals(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
assert.NotEqual(cli.AppHelpTemplate, "")
|
|
assert.NotNil(savedCLIVersionPrinter)
|
|
assert.NotNil(savedCLIErrWriter)
|
|
|
|
cli.AppHelpTemplate = ""
|
|
cli.VersionPrinter = nil
|
|
cli.ErrWriter = nil
|
|
|
|
resetCLIGlobals()
|
|
|
|
assert.Equal(cli.AppHelpTemplate, savedCLIAppHelpTemplate)
|
|
assert.NotNil(cli.VersionPrinter)
|
|
assert.NotNil(savedCLIVersionPrinter)
|
|
}
|
|
|
|
func createTempContainerIDMapping(containerID, sandboxID string) (string, error) {
|
|
tmpDir, err := ioutil.TempDir("", "containers-mapping")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
ctrsMapTreePath = tmpDir
|
|
|
|
path := filepath.Join(ctrsMapTreePath, containerID, sandboxID)
|
|
if err := os.MkdirAll(path, 0750); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return tmpDir, nil
|
|
}
|