mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-09-29 04:25:40 +00:00
kata-runtime list command should list all valid container, not fail when some containers information uncorrent, like rootfs not found. Fixes: #1592 Signed-off-by: Ace-Tang <aceapril@126.com>
786 lines
18 KiB
Go
786 lines
18 KiB
Go
// Copyright (c) 2017 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/kata-containers/runtime/pkg/katautils"
|
|
vc "github.com/kata-containers/runtime/virtcontainers"
|
|
vcAnnotations "github.com/kata-containers/runtime/virtcontainers/pkg/annotations"
|
|
"github.com/kata-containers/runtime/virtcontainers/pkg/vcmock"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
type TestFileWriter struct {
|
|
Name string
|
|
File *os.File
|
|
}
|
|
|
|
var hypervisorDetails1 = hypervisorDetails{
|
|
HypervisorAsset: asset{
|
|
Path: "/hypervisor/path",
|
|
},
|
|
ImageAsset: asset{
|
|
Path: "/image/path",
|
|
},
|
|
KernelAsset: asset{
|
|
Path: "/kernel/path",
|
|
},
|
|
}
|
|
|
|
var hypervisorDetails2 = hypervisorDetails{
|
|
HypervisorAsset: asset{
|
|
Path: "/hypervisor/path2",
|
|
},
|
|
ImageAsset: asset{
|
|
Path: "/image/path2",
|
|
},
|
|
KernelAsset: asset{
|
|
Path: "/kernel/path2",
|
|
},
|
|
}
|
|
|
|
var hypervisorDetails3 = hypervisorDetails{
|
|
HypervisorAsset: asset{
|
|
Path: "/hypervisor/path3",
|
|
},
|
|
ImageAsset: asset{
|
|
Path: "/image/path3",
|
|
},
|
|
KernelAsset: asset{
|
|
Path: "/kernel/path3",
|
|
},
|
|
}
|
|
|
|
var testStatuses = []fullContainerState{
|
|
{
|
|
containerState: containerState{
|
|
Version: "",
|
|
ID: "1",
|
|
InitProcessPid: 1234,
|
|
Status: "running",
|
|
Bundle: "/somewhere/over/the/rainbow",
|
|
Created: time.Now().UTC(),
|
|
Annotations: map[string]string(nil),
|
|
Owner: "#0",
|
|
},
|
|
|
|
CurrentHypervisorDetails: hypervisorDetails1,
|
|
LatestHypervisorDetails: hypervisorDetails1,
|
|
StaleAssets: []string{},
|
|
},
|
|
{
|
|
containerState: containerState{
|
|
Version: "",
|
|
ID: "2",
|
|
InitProcessPid: 2345,
|
|
Status: "stopped",
|
|
Bundle: "/this/path/is/invalid",
|
|
Created: time.Now().UTC(),
|
|
Annotations: map[string]string(nil),
|
|
Owner: "#0",
|
|
},
|
|
|
|
CurrentHypervisorDetails: hypervisorDetails2,
|
|
LatestHypervisorDetails: hypervisorDetails2,
|
|
StaleAssets: []string{},
|
|
},
|
|
{
|
|
containerState: containerState{
|
|
Version: "",
|
|
ID: "3",
|
|
InitProcessPid: 9999,
|
|
Status: "ready",
|
|
Bundle: "/foo/bar/baz",
|
|
Created: time.Now().UTC(),
|
|
Annotations: map[string]string(nil),
|
|
Owner: "#0",
|
|
},
|
|
|
|
CurrentHypervisorDetails: hypervisorDetails3,
|
|
LatestHypervisorDetails: hypervisorDetails3,
|
|
StaleAssets: []string{},
|
|
},
|
|
}
|
|
|
|
// Implement the io.Writer interface
|
|
func (w *TestFileWriter) Write(bytes []byte) (n int, err error) {
|
|
return w.File.Write(bytes)
|
|
}
|
|
|
|
func formatListDataAsBytes(formatter formatState, state []fullContainerState, showAll bool) (bytes []byte, err error) {
|
|
tmpfile, err := ioutil.TempFile("", "formatListData-")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer os.Remove(tmpfile.Name())
|
|
|
|
err = formatter.Write(state, showAll, tmpfile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tmpfile.Close()
|
|
|
|
return ioutil.ReadFile(tmpfile.Name())
|
|
}
|
|
|
|
func formatListDataAsString(formatter formatState, state []fullContainerState, showAll bool) (lines []string, err error) {
|
|
bytes, err := formatListDataAsBytes(formatter, state, showAll)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lines = strings.Split(string(bytes), "\n")
|
|
|
|
// Remove last line if empty
|
|
length := len(lines)
|
|
last := lines[length-1]
|
|
if last == "" {
|
|
lines = lines[:length-1]
|
|
}
|
|
|
|
return lines, nil
|
|
}
|
|
|
|
func TestStateToIDList(t *testing.T) {
|
|
|
|
// no header
|
|
expectedLength := len(testStatuses)
|
|
|
|
// showAll should not affect the output
|
|
for _, showAll := range []bool{true, false} {
|
|
lines, err := formatListDataAsString(&formatIDList{}, testStatuses, showAll)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var expected []string
|
|
for _, s := range testStatuses {
|
|
expected = append(expected, s.ID)
|
|
}
|
|
|
|
length := len(lines)
|
|
|
|
if length != expectedLength {
|
|
t.Fatalf("Expected %d lines, got %d: %v", expectedLength, length, lines)
|
|
}
|
|
|
|
assert.Equal(t, lines, expected, "lines + expected")
|
|
}
|
|
}
|
|
|
|
func TestStateToTabular(t *testing.T) {
|
|
// +1 for header line
|
|
expectedLength := len(testStatuses) + 1
|
|
|
|
expectedDefaultHeaderPattern := `\AID\s+PID\s+STATUS\s+BUNDLE\s+CREATED\s+OWNER`
|
|
expectedExtendedHeaderPattern := `HYPERVISOR\s+KERNEL\s+IMAGE\s+LATEST-KERNEL\s+LATEST-IMAGE\s+STALE`
|
|
endingPattern := `\s*\z`
|
|
|
|
lines, err := formatListDataAsString(&formatTabular{}, testStatuses, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
length := len(lines)
|
|
|
|
expectedHeaderPattern := expectedDefaultHeaderPattern + endingPattern
|
|
expectedHeaderRE := regexp.MustCompile(expectedHeaderPattern)
|
|
|
|
if length != expectedLength {
|
|
t.Fatalf("Expected %d lines, got %d", expectedLength, length)
|
|
}
|
|
|
|
header := lines[0]
|
|
|
|
matches := expectedHeaderRE.FindAllStringSubmatch(header, -1)
|
|
if matches == nil {
|
|
t.Fatalf("Header line failed to match:\n"+
|
|
"pattern : %v\n"+
|
|
"line : %v\n",
|
|
expectedDefaultHeaderPattern,
|
|
header)
|
|
}
|
|
|
|
for i, status := range testStatuses {
|
|
lineIndex := i + 1
|
|
line := lines[lineIndex]
|
|
|
|
expectedLinePattern := fmt.Sprintf(`\A%s\s+%d\s+%s\s+%s\s+%s\s+%s\s*\z`,
|
|
regexp.QuoteMeta(status.ID),
|
|
status.InitProcessPid,
|
|
regexp.QuoteMeta(status.Status),
|
|
regexp.QuoteMeta(status.Bundle),
|
|
regexp.QuoteMeta(status.Created.Format(time.RFC3339Nano)),
|
|
regexp.QuoteMeta(status.Owner))
|
|
|
|
expectedLineRE := regexp.MustCompile(expectedLinePattern)
|
|
|
|
matches := expectedLineRE.FindAllStringSubmatch(line, -1)
|
|
if matches == nil {
|
|
t.Fatalf("Data line failed to match:\n"+
|
|
"pattern : %v\n"+
|
|
"line : %v\n",
|
|
expectedLinePattern,
|
|
line)
|
|
}
|
|
}
|
|
|
|
// Try again with full details this time
|
|
lines, err = formatListDataAsString(&formatTabular{}, testStatuses, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
length = len(lines)
|
|
|
|
expectedHeaderPattern = expectedDefaultHeaderPattern + `\s+` + expectedExtendedHeaderPattern + endingPattern
|
|
expectedHeaderRE = regexp.MustCompile(expectedHeaderPattern)
|
|
|
|
if length != expectedLength {
|
|
t.Fatalf("Expected %d lines, got %d", expectedLength, length)
|
|
}
|
|
|
|
header = lines[0]
|
|
|
|
matches = expectedHeaderRE.FindAllStringSubmatch(header, -1)
|
|
if matches == nil {
|
|
t.Fatalf("Header line failed to match:\n"+
|
|
"pattern : %v\n"+
|
|
"line : %v\n",
|
|
expectedDefaultHeaderPattern,
|
|
header)
|
|
}
|
|
|
|
for i, status := range testStatuses {
|
|
lineIndex := i + 1
|
|
line := lines[lineIndex]
|
|
|
|
expectedLinePattern := fmt.Sprintf(`\A%s\s+%d\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s\s*\z`,
|
|
regexp.QuoteMeta(status.ID),
|
|
status.InitProcessPid,
|
|
regexp.QuoteMeta(status.Status),
|
|
regexp.QuoteMeta(status.Bundle),
|
|
regexp.QuoteMeta(status.Created.Format(time.RFC3339Nano)),
|
|
regexp.QuoteMeta(status.Owner),
|
|
regexp.QuoteMeta(status.CurrentHypervisorDetails.HypervisorAsset.Path),
|
|
regexp.QuoteMeta(status.CurrentHypervisorDetails.KernelAsset.Path),
|
|
regexp.QuoteMeta(status.CurrentHypervisorDetails.ImageAsset.Path),
|
|
regexp.QuoteMeta(status.LatestHypervisorDetails.KernelAsset.Path),
|
|
regexp.QuoteMeta(status.LatestHypervisorDetails.ImageAsset.Path),
|
|
regexp.QuoteMeta("-"))
|
|
|
|
expectedLineRE := regexp.MustCompile(expectedLinePattern)
|
|
|
|
matches := expectedLineRE.FindAllStringSubmatch(line, -1)
|
|
if matches == nil {
|
|
t.Fatalf("Data line failed to match:\n"+
|
|
"pattern : %v\n"+
|
|
"line : %v\n",
|
|
expectedLinePattern,
|
|
line)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStateToJSON(t *testing.T) {
|
|
expectedLength := len(testStatuses)
|
|
|
|
// showAll should not affect the output
|
|
for _, showAll := range []bool{true, false} {
|
|
bytes, err := formatListDataAsBytes(&formatJSON{}, testStatuses, showAll)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Force capacity to match the original otherwise assert.Equal() complains.
|
|
states := make([]fullContainerState, 0, len(testStatuses))
|
|
|
|
err = json.Unmarshal(bytes, &states)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
length := len(states)
|
|
|
|
if length != expectedLength {
|
|
t.Fatalf("Expected %d lines, got %d", expectedLength, length)
|
|
}
|
|
|
|
// golang tip (what will presumably become v1.9) now
|
|
// stores a monotonic clock value as part of time.Time's
|
|
// internal representation (this is shown by a suffix in
|
|
// the form "m=±ddd.nnnnnnnnn" when calling String() on
|
|
// the time.Time object). However, this monotonic value
|
|
// is stripped out when marshaling.
|
|
//
|
|
// This behaviour change makes comparing the original
|
|
// object and the marshaled-and-then-unmarshaled copy of
|
|
// the object doomed to failure.
|
|
//
|
|
// The solution? Manually strip the monotonic time out
|
|
// of the original before comparison (yuck!)
|
|
//
|
|
// See:
|
|
//
|
|
// - https://go-review.googlesource.com/c/36255/7/src/time/time.go#54
|
|
//
|
|
for i := 0; i < expectedLength; i++ {
|
|
// remove monotonic time part
|
|
testStatuses[i].Created = testStatuses[i].Created.Truncate(0)
|
|
}
|
|
|
|
assert.Equal(t, states, testStatuses, "states + testStatuses")
|
|
}
|
|
}
|
|
|
|
func TestListCLIFunctionNoContainers(t *testing.T) {
|
|
ctx := createCLIContext(nil)
|
|
ctx.App.Name = "foo"
|
|
ctx.App.Metadata["foo"] = "bar"
|
|
|
|
fn, ok := listCLICommand.Action.(func(context *cli.Context) error)
|
|
assert.True(t, ok)
|
|
|
|
err := fn(ctx)
|
|
|
|
// no config in the Metadata
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestListGetContainersListSandboxFail(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
ctx := createCLIContext(nil)
|
|
ctx.App.Name = "foo"
|
|
|
|
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
|
|
assert.NoError(err)
|
|
|
|
ctx.App.Metadata = map[string]interface{}{
|
|
"runtimeConfig": runtimeConfig,
|
|
}
|
|
|
|
_, err = getContainers(context.Background(), ctx)
|
|
assert.Error(err)
|
|
assert.True(vcmock.IsMockError(err))
|
|
}
|
|
|
|
func TestListGetContainers(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
testingImpl.ListSandboxFunc = func(ctx context.Context) ([]vc.SandboxStatus, error) {
|
|
// No pre-existing sandboxes
|
|
return []vc.SandboxStatus{}, nil
|
|
}
|
|
|
|
defer func() {
|
|
testingImpl.ListSandboxFunc = nil
|
|
}()
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
ctx := createCLIContext(nil)
|
|
ctx.App.Name = "foo"
|
|
|
|
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
|
|
assert.NoError(err)
|
|
|
|
ctx.App.Metadata = map[string]interface{}{
|
|
"runtimeConfig": runtimeConfig,
|
|
}
|
|
|
|
state, err := getContainers(context.Background(), ctx)
|
|
assert.NoError(err)
|
|
assert.Equal(state, []fullContainerState(nil))
|
|
}
|
|
|
|
func TestListGetContainersSandboxWithoutContainers(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
sandbox := &vcmock.Sandbox{
|
|
MockID: testSandboxID,
|
|
}
|
|
|
|
testingImpl.ListSandboxFunc = func(ctx context.Context) ([]vc.SandboxStatus, error) {
|
|
return []vc.SandboxStatus{
|
|
{
|
|
ID: sandbox.ID(),
|
|
ContainersStatus: []vc.ContainerStatus(nil),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
defer func() {
|
|
testingImpl.ListSandboxFunc = nil
|
|
}()
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
ctx := createCLIContext(nil)
|
|
ctx.App.Name = "foo"
|
|
|
|
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
|
|
assert.NoError(err)
|
|
|
|
ctx.App.Metadata = map[string]interface{}{
|
|
"runtimeConfig": runtimeConfig,
|
|
}
|
|
|
|
state, err := getContainers(context.Background(), ctx)
|
|
assert.NoError(err)
|
|
assert.Equal(state, []fullContainerState(nil))
|
|
}
|
|
|
|
func TestListGetContainersSandboxWithContainer(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
sandbox := &vcmock.Sandbox{
|
|
MockID: testSandboxID,
|
|
}
|
|
|
|
rootfs := filepath.Join(tmpdir, "rootfs")
|
|
err = os.MkdirAll(rootfs, testDirMode)
|
|
assert.NoError(err)
|
|
|
|
testingImpl.ListSandboxFunc = func(ctx context.Context) ([]vc.SandboxStatus, error) {
|
|
return []vc.SandboxStatus{
|
|
{
|
|
ID: sandbox.ID(),
|
|
ContainersStatus: []vc.ContainerStatus{
|
|
{
|
|
ID: sandbox.ID(),
|
|
Annotations: map[string]string{},
|
|
RootFs: rootfs,
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
defer func() {
|
|
testingImpl.ListSandboxFunc = nil
|
|
}()
|
|
|
|
ctx := createCLIContext(nil)
|
|
ctx.App.Name = "foo"
|
|
|
|
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
|
|
assert.NoError(err)
|
|
|
|
ctx.App.Metadata["runtimeConfig"] = runtimeConfig
|
|
|
|
_, err = getContainers(context.Background(), ctx)
|
|
assert.NoError(err)
|
|
}
|
|
|
|
func TestListCLIFunctionFormatFail(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
quietFlags := flag.NewFlagSet("test", 0)
|
|
quietFlags.Bool("quiet", true, "")
|
|
|
|
tableFlags := flag.NewFlagSet("test", 0)
|
|
tableFlags.String("format", "table", "")
|
|
|
|
jsonFlags := flag.NewFlagSet("test", 0)
|
|
jsonFlags.String("format", "json", "")
|
|
|
|
invalidFlags := flag.NewFlagSet("test", 0)
|
|
invalidFlags.String("format", "not-a-valid-format", "")
|
|
|
|
type testData struct {
|
|
format string
|
|
flags *flag.FlagSet
|
|
}
|
|
|
|
data := []testData{
|
|
{"quiet", quietFlags},
|
|
{"table", tableFlags},
|
|
{"json", jsonFlags},
|
|
{"invalid", invalidFlags},
|
|
}
|
|
|
|
sandbox := &vcmock.Sandbox{
|
|
MockID: testSandboxID,
|
|
}
|
|
|
|
rootfs := filepath.Join(tmpdir, "rootfs")
|
|
|
|
testingImpl.ListSandboxFunc = func(ctx context.Context) ([]vc.SandboxStatus, error) {
|
|
return []vc.SandboxStatus{
|
|
{
|
|
ID: sandbox.ID(),
|
|
ContainersStatus: []vc.ContainerStatus{
|
|
{
|
|
ID: sandbox.ID(),
|
|
Annotations: map[string]string{
|
|
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
|
},
|
|
RootFs: rootfs,
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
defer func() {
|
|
testingImpl.ListSandboxFunc = nil
|
|
}()
|
|
|
|
savedOutputFile := defaultOutputFile
|
|
defer func() {
|
|
defaultOutputFile = savedOutputFile
|
|
}()
|
|
|
|
// purposely invalid
|
|
var invalidFile *os.File
|
|
|
|
for _, d := range data {
|
|
// start off with an invalid output file
|
|
defaultOutputFile = invalidFile
|
|
|
|
ctx := createCLIContext(d.flags)
|
|
ctx.App.Name = "foo"
|
|
ctx.App.Metadata["foo"] = "bar"
|
|
|
|
fn, ok := listCLICommand.Action.(func(context *cli.Context) error)
|
|
assert.True(ok, d)
|
|
|
|
err = fn(ctx)
|
|
|
|
// no config in the Metadata
|
|
assert.Error(err, d)
|
|
|
|
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
|
|
assert.NoError(err, d)
|
|
|
|
ctx.App.Metadata["runtimeConfig"] = runtimeConfig
|
|
|
|
err = os.MkdirAll(rootfs, testDirMode)
|
|
assert.NoError(err)
|
|
|
|
err = fn(ctx)
|
|
|
|
// invalid output file
|
|
assert.Error(err, d)
|
|
assert.False(vcmock.IsMockError(err), d)
|
|
|
|
output := filepath.Join(tmpdir, "output")
|
|
f, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE, testFileMode)
|
|
assert.NoError(err)
|
|
defer f.Close()
|
|
|
|
// output file is now valid
|
|
defaultOutputFile = f
|
|
|
|
err = fn(ctx)
|
|
if d.format == "invalid" {
|
|
assert.Error(err)
|
|
assert.False(vcmock.IsMockError(err), d)
|
|
} else {
|
|
assert.NoError(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestListCLIFunctionQuiet(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
|
|
assert.NoError(err)
|
|
|
|
sandbox := &vcmock.Sandbox{
|
|
MockID: testSandboxID,
|
|
}
|
|
|
|
rootfs := filepath.Join(tmpdir, "rootfs")
|
|
err = os.MkdirAll(rootfs, testDirMode)
|
|
assert.NoError(err)
|
|
|
|
testingImpl.ListSandboxFunc = func(ctx context.Context) ([]vc.SandboxStatus, error) {
|
|
return []vc.SandboxStatus{
|
|
{
|
|
ID: sandbox.ID(),
|
|
ContainersStatus: []vc.ContainerStatus{
|
|
{
|
|
ID: sandbox.ID(),
|
|
Annotations: map[string]string{
|
|
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
|
},
|
|
RootFs: rootfs,
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
defer func() {
|
|
testingImpl.ListSandboxFunc = nil
|
|
}()
|
|
|
|
set := flag.NewFlagSet("test", 0)
|
|
set.Bool("quiet", true, "")
|
|
|
|
ctx := createCLIContext(set)
|
|
ctx.App.Name = "foo"
|
|
ctx.App.Metadata["runtimeConfig"] = runtimeConfig
|
|
|
|
savedOutputFile := defaultOutputFile
|
|
defer func() {
|
|
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
|
|
|
|
fn, ok := listCLICommand.Action.(func(context *cli.Context) error)
|
|
assert.True(ok)
|
|
|
|
err = fn(ctx)
|
|
assert.NoError(err)
|
|
f.Close()
|
|
|
|
text, err := katautils.GetFileContents(output)
|
|
assert.NoError(err)
|
|
|
|
trimmed := strings.TrimSpace(text)
|
|
assert.Equal(testSandboxID, trimmed)
|
|
}
|
|
|
|
func TestListGetDirOwner(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
_, err = getDirOwner("")
|
|
// invalid parameter
|
|
assert.Error(err)
|
|
|
|
dir := filepath.Join(tmpdir, "dir")
|
|
|
|
_, err = getDirOwner(dir)
|
|
// ENOENT
|
|
assert.Error(err)
|
|
|
|
err = createEmptyFile(dir)
|
|
assert.NoError(err)
|
|
|
|
_, err = getDirOwner(dir)
|
|
// wrong file type
|
|
assert.Error(err)
|
|
|
|
err = os.Remove(dir)
|
|
assert.NoError(err)
|
|
|
|
err = os.MkdirAll(dir, testDirMode)
|
|
assert.NoError(err)
|
|
|
|
uid := uint32(os.Getuid())
|
|
|
|
dirUID, err := getDirOwner(dir)
|
|
assert.NoError(err)
|
|
assert.Equal(dirUID, uid)
|
|
}
|
|
|
|
func TestListWithRootfsMissShouldSuccess(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
sandbox := &vcmock.Sandbox{
|
|
MockID: testSandboxID,
|
|
}
|
|
|
|
rootfs := filepath.Join(tmpdir, "rootfs")
|
|
err = os.MkdirAll(rootfs, testDirMode)
|
|
assert.NoError(err)
|
|
|
|
testingImpl.ListSandboxFunc = func(ctx context.Context) ([]vc.SandboxStatus, error) {
|
|
return []vc.SandboxStatus{
|
|
{
|
|
ID: sandbox.ID(),
|
|
ContainersStatus: []vc.ContainerStatus{
|
|
{
|
|
ID: sandbox.ID(),
|
|
Annotations: map[string]string{
|
|
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
|
},
|
|
RootFs: rootfs,
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
defer func() {
|
|
testingImpl.ListSandboxFunc = nil
|
|
}()
|
|
|
|
set := flag.NewFlagSet("test", 0)
|
|
set.String("format", "table", "")
|
|
ctx := createCLIContext(set)
|
|
ctx.App.Name = "foo"
|
|
|
|
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
|
|
assert.NoError(err)
|
|
|
|
ctx.App.Metadata["runtimeConfig"] = runtimeConfig
|
|
|
|
fn, ok := listCLICommand.Action.(func(context *cli.Context) error)
|
|
assert.True(ok)
|
|
|
|
err = fn(ctx)
|
|
assert.NoError(err)
|
|
|
|
// remove container rootfs, check list command should also work
|
|
assert.NoError(os.RemoveAll(rootfs))
|
|
err = fn(ctx)
|
|
assert.NoError(err)
|
|
}
|