mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-07-16 16:32:03 +00:00
cli: Add initial cli implementation.
- Add kata-runtime - Add unit test - Add Makefile to build cli Fixes: #33 Signed-off-by: Julio Montes <julio.montes@intel.com> Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com> Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
This commit is contained in:
parent
65b9936798
commit
e84a9a70b0
23
.ci/go-no-os-exit.sh
Executable file
23
.ci/go-no-os-exit.sh
Executable file
@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
go_packages=.
|
||||
|
||||
candidates=`go list -f '{{.Dir}}/*.go' $go_packages`
|
||||
for f in $candidates; do
|
||||
filename=`basename $f`
|
||||
# skip exit.go where, the only file we should call os.Exit() from.
|
||||
[[ $filename == "exit.go" ]] && continue
|
||||
# skip exit_test.go
|
||||
[[ $filename == "exit_test.go" ]] && continue
|
||||
# skip main_test.go
|
||||
[[ $filename == "main_test.go" ]] && continue
|
||||
files="$f $files"
|
||||
done
|
||||
|
||||
if egrep -n '\<os\.Exit\>' $files; then
|
||||
echo "Direct calls to os.Exit() are forbidden, please use exit() so atexit() works"
|
||||
exit 1
|
||||
fi
|
99
.ci/go-static-checks.sh
Executable file
99
.ci/go-static-checks.sh
Executable file
@ -0,0 +1,99 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -e
|
||||
|
||||
# Perform static go tests.
|
||||
|
||||
function usage {
|
||||
echo "Usage $0 [OPTIONS] [PACKAGES]"
|
||||
echo "Perform static go checks on PACKAGES (./... by default)."
|
||||
echo
|
||||
echo "List of options:"
|
||||
echo " -h, --help print this help"
|
||||
echo " -n, --no-network do not access the network"
|
||||
}
|
||||
|
||||
for i in "$@"; do
|
||||
case $i in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-n|--no-network)
|
||||
NONETWORK=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
args="$args $i"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
go_packages=$args
|
||||
|
||||
[ -z "$go_packages" ] && {
|
||||
go_packages=$(go list ./... | grep -v vendor)
|
||||
}
|
||||
|
||||
function install_package {
|
||||
url="$1"
|
||||
name=${url##*/}
|
||||
|
||||
if [ -n "$NONETWORK" ]; then
|
||||
echo "Skipping updating package $name, no network access allowed"
|
||||
return
|
||||
fi
|
||||
|
||||
echo Updating $name...
|
||||
go get -u $url
|
||||
}
|
||||
|
||||
install_package github.com/fzipp/gocyclo
|
||||
install_package github.com/client9/misspell/cmd/misspell
|
||||
install_package github.com/golang/lint/golint
|
||||
install_package github.com/gordonklaus/ineffassign
|
||||
install_package github.com/opennota/check/cmd/structcheck
|
||||
install_package honnef.co/go/tools/cmd/unused
|
||||
install_package honnef.co/go/tools/cmd/staticcheck
|
||||
|
||||
echo Doing go static checks on packages: $go_packages
|
||||
|
||||
echo "Running misspell..."
|
||||
go list -f '{{.Dir}}/*.go' $go_packages |\
|
||||
xargs -I % bash -c "misspell -error %"
|
||||
|
||||
echo "Running go vet..."
|
||||
go vet $go_packages
|
||||
|
||||
cmd="gofmt -s -d -l"
|
||||
echo "Running gofmt..."
|
||||
|
||||
# Note: ignore git directory in case any refs end in ".go" too.
|
||||
diff=$(find . -not -wholename '*/vendor/*' -not -wholename '*/.git/*' -name '*.go' | \
|
||||
xargs $cmd)
|
||||
if [ -n "$diff" -a $(echo "$diff" | wc -l) -ne 0 ]
|
||||
then
|
||||
echo 2>&1 "ERROR: '$cmd' found problems:"
|
||||
echo 2>&1 "$diff"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Running cyclo..."
|
||||
gocyclo -over 15 `go list -f '{{.Dir}}/*.go' $go_packages`
|
||||
|
||||
echo "Running golint..."
|
||||
for p in $go_packages; do golint -set_exit_status $p; done
|
||||
|
||||
echo "Running ineffassign..."
|
||||
go list -f '{{.Dir}}' $go_packages | xargs -L 1 ineffassign
|
||||
|
||||
for tool in structcheck unused staticcheck
|
||||
do
|
||||
echo "Running ${tool}..."
|
||||
eval "$tool" "$go_packages"
|
||||
done
|
||||
|
||||
echo "All Good!"
|
96
.ci/go-test.sh
Executable file
96
.ci/go-test.sh
Executable file
@ -0,0 +1,96 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -e
|
||||
|
||||
script_dir=$(cd `dirname $0`; pwd)
|
||||
root_dir=`dirname $script_dir`
|
||||
|
||||
test_packages="."
|
||||
|
||||
# Set default test run timeout value.
|
||||
#
|
||||
# CC_GO_TEST_TIMEOUT can be set to any value accepted by
|
||||
# "go test -timeout X"
|
||||
timeout_value=${CC_GO_TEST_TIMEOUT:-10s}
|
||||
|
||||
go_test_flags="-v -race -timeout $timeout_value"
|
||||
cov_file="profile.cov"
|
||||
tmp_cov_file="profile_tmp.cov"
|
||||
|
||||
# Run a command as either root or the current user (which might still be root).
|
||||
#
|
||||
# If the first argument is "root", run using sudo, else run as normal.
|
||||
# All arguments after the first will be treated as the command to run.
|
||||
function run_as_user
|
||||
{
|
||||
user="$1"
|
||||
shift
|
||||
cmd=$*
|
||||
|
||||
if [ "$user" = root ]
|
||||
then
|
||||
# use a shell to ensure PATH is correct.
|
||||
sudo -E PATH="$PATH" sh -c "$cmd"
|
||||
else
|
||||
$cmd
|
||||
fi
|
||||
}
|
||||
|
||||
function test_html_coverage
|
||||
{
|
||||
html_report="coverage.html"
|
||||
|
||||
test_coverage
|
||||
|
||||
go tool cover -html="${cov_file}" -o "${html_report}"
|
||||
rm -f "${cov_file}"
|
||||
|
||||
run_as_user "current" chmod 644 "${html_report}"
|
||||
}
|
||||
|
||||
function test_coverage
|
||||
{
|
||||
echo "mode: atomic" > "$cov_file"
|
||||
|
||||
if [ $(id -u) -eq 0 ]
|
||||
then
|
||||
echo >&2 "WARNING: Already running as root so will not re-run tests as non-root user."
|
||||
echo >&2 "WARNING: As a result, only a subset of tests will be run"
|
||||
echo >&2 "WARNING: (run this script as a non-privileged to ensure all tests are run)."
|
||||
users="current"
|
||||
else
|
||||
# Run the unit-tests *twice* (since some must run as root and
|
||||
# others must run as non-root), combining the resulting test
|
||||
# coverage files.
|
||||
users="current root"
|
||||
fi
|
||||
|
||||
for pkg in $test_packages; do
|
||||
for user in $users; do
|
||||
printf "INFO: Running 'go test' as %s user on packages '%s' with flags '%s'\n" "$user" "$test_packages" "$go_test_flags"
|
||||
|
||||
run_as_user "$user" go test $go_test_flags -covermode=atomic -coverprofile="$tmp_cov_file" $pkg
|
||||
if [ -f "${tmp_cov_file}" ]; then
|
||||
run_as_user "$user" chmod 644 "$tmp_cov_file"
|
||||
tail -n +2 "$tmp_cov_file" >> "$cov_file"
|
||||
run_as_user "$user" rm -f "$tmp_cov_file"
|
||||
fi
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
function test_local
|
||||
{
|
||||
go test $go_test_flags $test_packages
|
||||
}
|
||||
|
||||
if [ "$1" = "html-coverage" ]; then
|
||||
test_html_coverage
|
||||
elif [ "$CI" = "true" ]; then
|
||||
test_coverage
|
||||
else
|
||||
test_local
|
||||
fi
|
8
cli/.gitignore
vendored
Normal file
8
cli/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/kata-runtime
|
||||
/coverage.html
|
||||
/config-generated.go
|
||||
/data/kata-collect-data.sh
|
||||
/data/kata-collect-data.sh.in
|
||||
/data/completions/bash/kata-runtime
|
||||
/data/completions/bash/kata-runtime.in
|
||||
/config/configuration.toml
|
584
cli/Makefile
Normal file
584
cli/Makefile
Normal file
@ -0,0 +1,584 @@
|
||||
# Copyright (c) 2017-2018 Intel Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
MAKEFILE_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
# Determine the lower-case name of the distro
|
||||
distro := $(shell \
|
||||
for file in /etc/os-release /usr/lib/os-release; do \
|
||||
if [ -e $$file ]; then \
|
||||
grep ^ID= $$file|cut -d= -f2-|tr -d '"'; \
|
||||
break; \
|
||||
fi \
|
||||
done)
|
||||
|
||||
GOARCH=$(shell go env GOARCH)
|
||||
HOST_ARCH=$(shell arch)
|
||||
|
||||
ifeq ($(ARCH),)
|
||||
ARCH = $(GOARCH)
|
||||
endif
|
||||
|
||||
ARCH_DIR = arch
|
||||
ARCH_FILE_SUFFIX = -options.mk
|
||||
ARCH_FILE = $(ARCH_DIR)/$(ARCH)$(ARCH_FILE_SUFFIX)
|
||||
ARCH_FILES = $(wildcard arch/*$(ARCH_FILE_SUFFIX))
|
||||
ALL_ARCHES = $(patsubst $(ARCH_DIR)/%$(ARCH_FILE_SUFFIX),%,$(ARCH_FILES))
|
||||
|
||||
# Load architecture-dependent settings
|
||||
include $(ARCH_FILE)
|
||||
|
||||
#------------------------------
|
||||
# project-specifics
|
||||
|
||||
# build type for Kata Containers
|
||||
KATA_TYPE = kata
|
||||
|
||||
KATA_PROJECT_NAME = Kata Containers
|
||||
KATA_PROJECT_TAG = kata-containers
|
||||
KATA_PROJECT_URL = https://github.com/kata-containers
|
||||
KATA_BUG_URL = $(KATA_PROJECT_URL)/kata-containers/issues/new
|
||||
|
||||
#------------------------------
|
||||
|
||||
# all supported project types
|
||||
PROJECT_TYPES = $(KATA_TYPE)
|
||||
|
||||
# If this environment variable is set to any value,
|
||||
# enable the Kata Containers system build.
|
||||
ifneq (,$(KATA_SYSTEM_BUILD))
|
||||
system_build_type = $(KATA_TYPE)
|
||||
endif
|
||||
|
||||
ifneq ($(SYSTEM_BUILD_TYPE),)
|
||||
ifeq ($(SYSTEM_BUILD_TYPE),$(KATA_TYPE))
|
||||
system_build_type = $(KATA_TYPE)
|
||||
endif
|
||||
endif
|
||||
|
||||
# A particular project system build is either triggered by
|
||||
# specifying a special target or via an environment variable.
|
||||
kata_system_build_requested := $(foreach f,\
|
||||
build-$(KATA_TYPE)-system install-$(KATA_TYPE)-system,\
|
||||
$(findstring $(f),$(MAKECMDGOALS)))
|
||||
ifneq (,$(strip $(kata_system_build_requested)))
|
||||
kata_system_build = yes
|
||||
else
|
||||
kata_system_build = no
|
||||
endif
|
||||
|
||||
ifeq ($(kata_system_build),yes)
|
||||
system_build_type = $(KATA_TYPE)
|
||||
endif
|
||||
|
||||
# If this environment variable is set to a valid build type,
|
||||
# use the value as the build type.
|
||||
|
||||
ifeq ($(system_build_type),)
|
||||
# Default built is for Kata
|
||||
PROJECT_TYPE = $(KATA_TYPE)
|
||||
|
||||
PROJECT_NAME = $(KATA_PROJECT_NAME)
|
||||
PROJECT_TAG = $(KATA_PROJECT_TAG)
|
||||
PROJECT_URL = $(KATA_PROJECT_URL)
|
||||
PROJECT_BUG_URL = $(KATA_BUG_URL)
|
||||
else
|
||||
ifeq ($(system_build_type),$(KATA_TYPE))
|
||||
PROJECT_TYPE = $(KATA_TYPE)
|
||||
|
||||
PROJECT_NAME = $(KATA_PROJECT_NAME)
|
||||
PROJECT_TAG = $(KATA_PROJECT_TAG)
|
||||
PROJECT_URL = $(KATA_PROJECT_URL)
|
||||
PROJECT_BUG_URL = $(KATA_BUG_URL)
|
||||
endif
|
||||
endif
|
||||
|
||||
BIN_PREFIX = $(PROJECT_TYPE)
|
||||
PROJECT_DIR = $(PROJECT_TAG)
|
||||
IMAGENAME = $(PROJECT_TAG).img
|
||||
|
||||
TARGET = $(BIN_PREFIX)-runtime
|
||||
DESTDIR :=
|
||||
|
||||
installing = $(findstring install,$(MAKECMDGOALS))
|
||||
|
||||
ifneq ($(system_build_type),)
|
||||
# Configure the build for a standard system that is
|
||||
# using OBS-generated packages.
|
||||
PREFIX := /usr
|
||||
BINDIR := $(PREFIX)/bin
|
||||
DESTBINDIR := /usr/local/bin
|
||||
QEMUBINDIR := $(BINDIR)
|
||||
SYSCONFDIR := /etc
|
||||
LOCALSTATEDIR := /var
|
||||
|
||||
ifeq (,$(installing))
|
||||
# Force a rebuild to ensure version details are correct
|
||||
# (but only for a non-install build phase).
|
||||
EXTRA_DEPS = clean
|
||||
endif
|
||||
else
|
||||
# standard build
|
||||
PREFIX := /usr/local
|
||||
BINDIR := $(PREFIX)/bin
|
||||
DESTBINDIR := $(DESTDIR)/$(BINDIR)
|
||||
QEMUBINDIR := $(BINDIR)
|
||||
SYSCONFDIR := $(PREFIX)/etc
|
||||
LOCALSTATEDIR := $(PREFIX)/var
|
||||
endif
|
||||
|
||||
LIBEXECDIR := $(PREFIX)/libexec
|
||||
SHAREDIR := $(PREFIX)/share
|
||||
DEFAULTSDIR := $(SHAREDIR)/defaults
|
||||
|
||||
PKGDATADIR := $(SHAREDIR)/$(PROJECT_DIR)
|
||||
PKGLIBDIR := $(LOCALSTATEDIR)/lib/$(PROJECT_DIR)
|
||||
PKGRUNDIR := $(LOCALSTATEDIR)/run/$(PROJECT_DIR)
|
||||
PKGLIBEXECDIR := $(LIBEXECDIR)/$(PROJECT_DIR)
|
||||
|
||||
KERNELPATH := $(PKGDATADIR)/vmlinuz.container
|
||||
IMAGEPATH := $(PKGDATADIR)/$(IMAGENAME)
|
||||
FIRMWAREPATH :=
|
||||
|
||||
QEMUPATH := $(QEMUBINDIR)/$(QEMUCMD)
|
||||
|
||||
SHIMCMD := $(BIN_PREFIX)-shim
|
||||
SHIMPATH := $(PKGLIBEXECDIR)/$(SHIMCMD)
|
||||
|
||||
PROXYCMD := $(BIN_PREFIX)-proxy
|
||||
PROXYPATH := $(PKGLIBEXECDIR)/$(PROXYCMD)
|
||||
|
||||
# Default number of vCPUs
|
||||
DEFVCPUS := 1
|
||||
# Default memory size in MiB
|
||||
DEFMEMSZ := 2048
|
||||
#Default number of bridges
|
||||
DEFBRIDGES := 1
|
||||
#Default network model
|
||||
DEFNETWORKMODEL := macvtap
|
||||
|
||||
DEFDISABLEBLOCK := false
|
||||
DEFBLOCKSTORAGEDRIVER := virtio-scsi
|
||||
DEFENABLEMEMPREALLOC := false
|
||||
DEFENABLEHUGEPAGES := false
|
||||
DEFENABLESWAP := false
|
||||
DEFENABLEDEBUG := false
|
||||
DEFDISABLENESTINGCHECKS := false
|
||||
|
||||
SED = sed
|
||||
|
||||
SOURCES := $(shell find . 2>&1 | grep -E '.*\.(c|h|go)$$')
|
||||
VERSION := ${shell cat ./VERSION}
|
||||
COMMIT_NO := $(shell git rev-parse HEAD 2> /dev/null || true)
|
||||
COMMIT := $(if $(shell git status --porcelain --untracked-files=no),${COMMIT_NO}-dirty,${COMMIT_NO})
|
||||
|
||||
CONFIG_FILE = configuration.toml
|
||||
CONFIG = config/$(CONFIG_FILE)
|
||||
CONFIG_IN = $(CONFIG).in
|
||||
|
||||
DESTTARGET := $(abspath $(DESTBINDIR)/$(TARGET))
|
||||
|
||||
DESTCONFDIR := $(DESTDIR)/$(DEFAULTSDIR)/$(PROJECT_DIR)
|
||||
DESTSYSCONFDIR := $(DESTDIR)/$(SYSCONFDIR)/$(PROJECT_DIR)
|
||||
|
||||
# Main configuration file location for stateless systems
|
||||
DESTCONFIG := $(abspath $(DESTCONFDIR)/$(CONFIG_FILE))
|
||||
|
||||
# Secondary configuration file location. Note that this takes precedence
|
||||
# over DESTCONFIG.
|
||||
DESTSYSCONFIG := $(abspath $(DESTSYSCONFDIR)/$(CONFIG_FILE))
|
||||
|
||||
DESTSHAREDIR := $(DESTDIR)/$(SHAREDIR)
|
||||
|
||||
SCRIPTS_DIR := $(BINDIR)
|
||||
|
||||
# list of variables the user may wish to override
|
||||
USER_VARS += ARCH
|
||||
USER_VARS += BASH_COMPLETIONSDIR
|
||||
USER_VARS += BINDIR
|
||||
USER_VARS += DESTCONFIG
|
||||
USER_VARS += DESTDIR
|
||||
USER_VARS += DESTSYSCONFIG
|
||||
USER_VARS += DESTTARGET
|
||||
USER_VARS += IMAGENAME
|
||||
USER_VARS += IMAGEPATH
|
||||
USER_VARS += MACHINETYPE
|
||||
USER_VARS += KATA_SYSTEM_BUILD
|
||||
USER_VARS += KERNELPATH
|
||||
USER_VARS += FIRMWAREPATH
|
||||
USER_VARS += MACHINEACCELERATORS
|
||||
USER_VARS += KERNELPARAMS
|
||||
USER_VARS += LIBEXECDIR
|
||||
USER_VARS += LOCALSTATEDIR
|
||||
USER_VARS += PKGDATADIR
|
||||
USER_VARS += PKGLIBDIR
|
||||
USER_VARS += PKGLIBEXECDIR
|
||||
USER_VARS += PKGRUNDIR
|
||||
USER_VARS += PREFIX
|
||||
USER_VARS += PROJECT_NAME
|
||||
USER_VARS += PROJECT_PREFIX
|
||||
USER_VARS += PROJECT_TYPE
|
||||
USER_VARS += PROXYPATH
|
||||
USER_VARS += QEMUBINDIR
|
||||
USER_VARS += QEMUCMD
|
||||
USER_VARS += QEMUPATH
|
||||
USER_VARS += SHAREDIR
|
||||
USER_VARS += SHIMPATH
|
||||
USER_VARS += SYSTEM_BUILD_TYPE
|
||||
USER_VARS += SYSCONFDIR
|
||||
USER_VARS += DEFVCPUS
|
||||
USER_VARS += DEFMEMSZ
|
||||
USER_VARS += DEFBRIDGES
|
||||
USER_VARS += DEFNETWORKMODEL
|
||||
USER_VARS += DEFDISABLEBLOCK
|
||||
USER_VARS += DEFBLOCKSTORAGEDRIVER
|
||||
USER_VARS += DEFENABLEMEMPREALLOC
|
||||
USER_VARS += DEFENABLEHUGEPAGES
|
||||
USER_VARS += DEFENABLESWAP
|
||||
USER_VARS += DEFENABLEDEBUG
|
||||
USER_VARS += DEFDISABLENESTINGCHECKS
|
||||
|
||||
V = @
|
||||
Q = $(V:1=)
|
||||
QUIET_BUILD = $(Q:@=@echo ' BUILD '$@;)
|
||||
QUIET_CHECK = $(Q:@=@echo ' CHECK '$@;)
|
||||
QUIET_CLEAN = $(Q:@=@echo ' CLEAN '$@;)
|
||||
QUIET_CONFIG = $(Q:@=@echo ' CONFIG '$@;)
|
||||
QUIET_GENERATE = $(Q:@=@echo ' GENERATE '$@;)
|
||||
QUIET_INST = $(Q:@=@echo ' INSTALL '$@;)
|
||||
QUIET_TEST = $(Q:@=@echo ' TEST '$@;)
|
||||
|
||||
# Return non-empty string if specified directory exists
|
||||
define DIR_EXISTS
|
||||
$(shell test -d $(1) && echo "$(1)")
|
||||
endef
|
||||
|
||||
# $1: name of architecture to display
|
||||
define SHOW_ARCH
|
||||
$(shell printf "\\t%s%s\\\n" "$(1)" $(if $(filter $(ARCH),$(1))," (default)",""))
|
||||
endef
|
||||
|
||||
# Only install git hooks if working in a git clone
|
||||
ifneq (,$(call DIR_EXISTS,.git))
|
||||
HANDLE_GIT_HOOKS = install-git-hooks
|
||||
endif
|
||||
|
||||
# Don't install hooks when running under the CI as they will stop the
|
||||
# tests from running.
|
||||
#
|
||||
# See: https://github.com/clearcontainers/runtime/issues/984
|
||||
ifneq (,$(CI))
|
||||
HANDLE_GIT_HOOKS =
|
||||
endif
|
||||
|
||||
default: $(TARGET) $(CONFIG) $(HANDLE_GIT_HOOKS)
|
||||
.DEFAULT: default
|
||||
|
||||
build: default
|
||||
|
||||
build-kata-system: default
|
||||
install-kata-system: install
|
||||
|
||||
define GENERATED_CODE
|
||||
// WARNING: This file is auto-generated - DO NOT EDIT!
|
||||
//
|
||||
// Note that some variables are "var" to allow them to be modified
|
||||
// by the tests.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// name is the name of the runtime
|
||||
const name = "$(TARGET)"
|
||||
|
||||
// name of the project
|
||||
const project = "$(PROJECT_NAME)"
|
||||
|
||||
// prefix used to denote non-standard CLI commands and options.
|
||||
const projectPrefix = "$(PROJECT_TYPE)"
|
||||
|
||||
// systemdUnitName is the systemd(1) target used to launch the agent.
|
||||
const systemdUnitName = "$(PROJECT_TAG).target"
|
||||
|
||||
// original URL for this project
|
||||
const projectURL = "$(PROJECT_URL)"
|
||||
|
||||
// commit is the git commit the runtime is compiled from.
|
||||
var commit = "$(COMMIT)"
|
||||
|
||||
// version is the runtime version.
|
||||
var version = "$(VERSION)"
|
||||
|
||||
// project-specific command names
|
||||
var envCmd = fmt.Sprintf("%s-env", projectPrefix)
|
||||
var checkCmd = fmt.Sprintf("%s-check", projectPrefix)
|
||||
|
||||
// project-specific option names
|
||||
var configFilePathOption = fmt.Sprintf("%s-config", projectPrefix)
|
||||
var showConfigPathsOption = fmt.Sprintf("%s-show-default-config-paths", projectPrefix)
|
||||
|
||||
var defaultHypervisorPath = "$(QEMUPATH)"
|
||||
var defaultImagePath = "$(IMAGEPATH)"
|
||||
var defaultKernelPath = "$(KERNELPATH)"
|
||||
var defaultFirmwarePath = "$(FIRMWAREPATH)"
|
||||
var defaultMachineAccelerators = "$(MACHINEACCELERATORS)"
|
||||
var defaultShimPath = "$(SHIMPATH)"
|
||||
|
||||
const defaultKernelParams = "$(KERNELPARAMS)"
|
||||
const defaultMachineType = "$(MACHINETYPE)"
|
||||
const defaultRootDirectory = "$(PKGRUNDIR)"
|
||||
|
||||
const defaultVCPUCount uint32 = $(DEFVCPUS)
|
||||
const defaultMemSize uint32 = $(DEFMEMSZ) // MiB
|
||||
const defaultBridgesCount uint32 = $(DEFBRIDGES)
|
||||
const defaultInterNetworkingModel = "$(DEFNETWORKMODEL)"
|
||||
const defaultDisableBlockDeviceUse bool = $(DEFDISABLEBLOCK)
|
||||
const defaultBlockDeviceDriver = "$(DEFBLOCKSTORAGEDRIVER)"
|
||||
const defaultEnableMemPrealloc bool = $(DEFENABLEMEMPREALLOC)
|
||||
const defaultEnableHugePages bool = $(DEFENABLEHUGEPAGES)
|
||||
const defaultEnableSwap bool = $(DEFENABLESWAP)
|
||||
const defaultEnableDebug bool = $(DEFENABLEDEBUG)
|
||||
const defaultDisableNestingChecks bool = $(DEFDISABLENESTINGCHECKS)
|
||||
|
||||
// Default config file used by stateless systems.
|
||||
var defaultRuntimeConfiguration = "$(DESTCONFIG)"
|
||||
|
||||
// Alternate config file that takes precedence over
|
||||
// defaultRuntimeConfiguration.
|
||||
var defaultSysConfRuntimeConfiguration = "$(DESTSYSCONFIG)"
|
||||
|
||||
var defaultProxyPath = "$(PROXYPATH)"
|
||||
endef
|
||||
|
||||
export GENERATED_CODE
|
||||
|
||||
#Install an executable file
|
||||
# params:
|
||||
# $1 : file to install
|
||||
# $2 : directory path where file will be installed
|
||||
define INSTALL_EXEC
|
||||
$(QUIET_INST)install -D $1 $(DESTDIR)$2/$(notdir $1);
|
||||
endef
|
||||
|
||||
GENERATED_GO_FILES += config-generated.go
|
||||
|
||||
config-generated.go: Makefile VERSION
|
||||
$(QUIET_GENERATE)echo "$$GENERATED_CODE" >$@
|
||||
|
||||
$(TARGET): $(EXTRA_DEPS) $(SOURCES) $(GENERATED_GO_FILES) $(GENERATED_FILES) Makefile | show-summary
|
||||
$(QUIET_BUILD)go build -i -o $@ .
|
||||
|
||||
.PHONY: \
|
||||
check \
|
||||
check-go-static \
|
||||
check-go-test \
|
||||
coverage \
|
||||
default \
|
||||
install \
|
||||
install-git-hooks \
|
||||
show-header \
|
||||
show-summary \
|
||||
show-variables
|
||||
|
||||
$(TARGET).coverage: $(SOURCES) $(GENERATED_FILES) Makefile
|
||||
$(QUIET_TEST)go test -o $@ -covermode count
|
||||
|
||||
GENERATED_FILES += $(CONFIG)
|
||||
|
||||
$(COLLECT_SCRIPT_PROJ): $(COLLECT_SCRIPT_SRC)
|
||||
$(QUIET_GENERATE)cp $< $(@)
|
||||
|
||||
$(BASH_COMPLETIONS_PROJ): $(BASH_COMPLETIONS_SRC)
|
||||
$(QUIET_GENERATE)cp $< $(@)
|
||||
|
||||
$(GENERATED_FILES): %: %.in Makefile VERSION
|
||||
$(QUIET_CONFIG)$(SED) \
|
||||
-e "s|@COMMIT@|$(COMMIT)|g" \
|
||||
-e "s|@VERSION@|$(VERSION)|g" \
|
||||
-e "s|@CONFIG_IN@|$(CONFIG_IN)|g" \
|
||||
-e "s|@DESTCONFIG@|$(DESTCONFIG)|g" \
|
||||
-e "s|@DESTSYSCONFIG@|$(DESTSYSCONFIG)|g" \
|
||||
-e "s|@IMAGEPATH@|$(IMAGEPATH)|g" \
|
||||
-e "s|@KERNELPATH@|$(KERNELPATH)|g" \
|
||||
-e "s|@FIRMWAREPATH@|$(FIRMWAREPATH)|g" \
|
||||
-e "s|@MACHINEACCELERATORS@|$(MACHINEACCELERATORS)|g" \
|
||||
-e "s|@KERNELPARAMS@|$(KERNELPARAMS)|g" \
|
||||
-e "s|@LOCALSTATEDIR@|$(LOCALSTATEDIR)|g" \
|
||||
-e "s|@PKGLIBEXECDIR@|$(PKGLIBEXECDIR)|g" \
|
||||
-e "s|@PROXYPATH@|$(PROXYPATH)|g" \
|
||||
-e "s|@PROJECT_BUG_URL@|$(PROJECT_BUG_URL)|g" \
|
||||
-e "s|@PROJECT_URL@|$(PROJECT_URL)|g" \
|
||||
-e "s|@PROJECT_NAME@|$(PROJECT_NAME)|g" \
|
||||
-e "s|@PROJECT_TAG@|$(PROJECT_TAG)|g" \
|
||||
-e "s|@PROJECT_TYPE@|$(PROJECT_TYPE)|g" \
|
||||
-e "s|@PROJECT_TYPES@|$(PROJECT_TYPES)|g" \
|
||||
-e "s|@QEMUPATH@|$(QEMUPATH)|g" \
|
||||
-e "s|@RUNTIME_NAME@|$(TARGET)|g" \
|
||||
-e "s|@MACHINETYPE@|$(MACHINETYPE)|g" \
|
||||
-e "s|@SHIMPATH@|$(SHIMPATH)|g" \
|
||||
-e "s|@DEFVCPUS@|$(DEFVCPUS)|g" \
|
||||
-e "s|@DEFMEMSZ@|$(DEFMEMSZ)|g" \
|
||||
-e "s|@DEFBRIDGES@|$(DEFBRIDGES)|g" \
|
||||
-e "s|@DEFNETWORKMODEL@|$(DEFNETWORKMODEL)|g" \
|
||||
-e "s|@DEFDISABLEBLOCK@|$(DEFDISABLEBLOCK)|g" \
|
||||
-e "s|@DEFBLOCKSTORAGEDRIVER@|$(DEFBLOCKSTORAGEDRIVER)|g" \
|
||||
-e "s|@DEFENABLEMEMPREALLOC@|$(DEFENABLEMEMPREALLOC)|g" \
|
||||
-e "s|@DEFENABLEHUGEPAGES@|$(DEFENABLEHUGEPAGES)|g" \
|
||||
-e "s|@DEFENABLEMSWAP@|$(DEFENABLESWAP)|g" \
|
||||
-e "s|@DEFENABLEDEBUG@|$(DEFENABLEDEBUG)|g" \
|
||||
-e "s|@DEFDISABLENESTINGCHECKS@|$(DEFDISABLENESTINGCHECKS)|g" \
|
||||
$< > $@
|
||||
|
||||
generate-config: $(CONFIG)
|
||||
|
||||
check: check-go-static check-go-test
|
||||
|
||||
check-go-test: $(GENERATED_FILES)
|
||||
$(QUIET_TEST)$(MAKEFILE_DIR)/../.ci/go-test.sh
|
||||
|
||||
check-go-static:
|
||||
$(QUIET_CHECK)$(MAKEFILE_DIR)/../.ci/go-static-checks.sh $(GO_STATIC_CHECKS_ARGS)
|
||||
$(QUIET_CHECK)$(MAKEFILE_DIR)/../.ci/go-no-os-exit.sh
|
||||
|
||||
coverage:
|
||||
$(QUIET_TEST)$(MAKEFILE_DIR)/../.ci/go-test.sh html-coverage
|
||||
|
||||
install: default install-scripts
|
||||
$(QUIET_INST)install -D $(TARGET) $(DESTTARGET)
|
||||
$(QUIET_INST)install -D $(CONFIG) $(DESTCONFIG)
|
||||
|
||||
install-scripts:
|
||||
$(foreach f,$(SCRIPTS),$(call INSTALL_EXEC,$f,$(SCRIPTS_DIR)))
|
||||
|
||||
clean:
|
||||
$(QUIET_CLEAN)rm -f $(TARGET) $(CONFIG) $(GENERATED_GO_FILES) $(GENERATED_FILES) $(COLLECT_SCRIPT_PROJ) $(BASH_COMPLETIONS_PROJ)
|
||||
|
||||
show-usage: show-header
|
||||
@printf "• Overview:\n"
|
||||
@printf "\n"
|
||||
@printf "\tTo build $(TARGET), just run, \"make\".\n"
|
||||
@printf "\n"
|
||||
@printf "\tFor a verbose build, run \"make V=1\".\n"
|
||||
@printf "\n"
|
||||
@printf "• Additional targets:\n"
|
||||
@printf "\n"
|
||||
@printf "\tbuild : standard build [1].\n"
|
||||
@printf "\tbuild-$(KATA_TYPE)-system : build using standard $(KATA_PROJECT_NAME) system paths.\n"
|
||||
@printf "\tcheck : run tests.\n"
|
||||
@printf "\tclean : remove built files.\n"
|
||||
@printf "\tcoverage : run coverage tests.\n"
|
||||
@printf "\tdefault : same as 'make build' (or just 'make').\n"
|
||||
@printf "\tgenerate-config : create configuration file.\n"
|
||||
@printf "\tinstall : install files [2].\n"
|
||||
@printf "\tinstall-$(KATA_TYPE)-system : install using standard $(KATA_PROJECT_NAME) system paths.\n"
|
||||
@printf "\tshow-arches : show supported architectures (ARCH variable values).\n"
|
||||
@printf "\tshow-summary : show install locations.\n"
|
||||
@printf "\n"
|
||||
@printf " Notes:\n"
|
||||
@printf "\n"
|
||||
@printf " [1] - Equivalent to:\n"
|
||||
@printf " - 'build-kata-system' if KATA_SYSTEM_BUILD is set.\n"
|
||||
@printf " - 'build' for the project specified by SYSTEM_BUILD_TYPE.\n"
|
||||
@printf "\n"
|
||||
@printf " [2] - Equivalent to:\n"
|
||||
@printf " - 'install-kata-system' if KATA_SYSTEM_BUILD is set.\n"
|
||||
@printf " - 'install' for the project specified by SYSTEM_BUILD_TYPE.\n"
|
||||
@printf "\n"
|
||||
|
||||
handle_help: show-usage show-summary show-variables show-footer
|
||||
|
||||
usage: handle_help
|
||||
help: handle_help
|
||||
|
||||
show-variables:
|
||||
@printf "• Variables affecting the build:\n\n"
|
||||
@printf \
|
||||
"$(foreach v,$(sort $(USER_VARS)),$(shell printf "\\t$(v)='$($(v))'\\\n"))"
|
||||
@printf "\n"
|
||||
|
||||
show-header:
|
||||
@printf "%s - version %s (commit %s)\n\n" $(TARGET) $(VERSION) $(COMMIT)
|
||||
|
||||
show-arches: show-header
|
||||
@printf "Supported architectures (possible values for ARCH variable):\n\n"
|
||||
@printf \
|
||||
"$(foreach v,$(ALL_ARCHES),$(call SHOW_ARCH,$(v)))\n"
|
||||
|
||||
show-footer:
|
||||
@printf "• Project:\n"
|
||||
@printf "\tHome: $(PROJECT_URL)\n"
|
||||
@printf "\tBugs: $(PROJECT_BUG_URL)\n\n"
|
||||
|
||||
show-summary: show-header
|
||||
@printf "• architecture:\n"
|
||||
@printf "\tHost: $(HOST_ARCH)\n"
|
||||
@printf "\tgolang: $(GOARCH)\n"
|
||||
@printf "\tBuild: $(ARCH)\n"
|
||||
@printf "\n"
|
||||
@printf "• golang:\n"
|
||||
@printf "\t"
|
||||
@go version
|
||||
@printf "\n"
|
||||
@printf "• Summary:\n"
|
||||
@printf "\n"
|
||||
@printf "\tProject system build type : $(system_build_type)\n"
|
||||
@printf "\n"
|
||||
@printf "\tbinary install path (DESTTARGET) : %s\n" $(DESTTARGET)
|
||||
@printf "\tconfig install path (DESTCONFIG) : %s\n" $(DESTCONFIG)
|
||||
@printf "\talternate config path (DESTSYSCONFIG) : %s\n" $(DESTSYSCONFIG)
|
||||
@printf "\thypervisor path (QEMUPATH) : %s\n" $(QEMUPATH)
|
||||
@printf "\tassets path (PKGDATADIR) : %s\n" $(PKGDATADIR)
|
||||
@printf "\tproxy+shim path (PKGLIBEXECDIR) : %s\n" $(PKGLIBEXECDIR)
|
||||
@printf "\n"
|
||||
|
||||
|
||||
# The following git hooks handle HEAD changes:
|
||||
# post-checkout <prev_head> <new_head> <file_or_branch_checkout>
|
||||
# post-commit # no parameters
|
||||
# post-merge <squash_or_not>
|
||||
# post-rewrite <amend_or_rebase>
|
||||
#
|
||||
define GIT_HOOK_POST_CHECKOUT
|
||||
#!/usr/bin/env bash
|
||||
prev_head=$$1
|
||||
new_head=$$2
|
||||
[[ "$$prev_head" == "$$new_head" ]] && exit
|
||||
printf "\nexecuting post-checkout git hook\n\n"
|
||||
rm -f config-generated.go
|
||||
endef
|
||||
export GIT_HOOK_POST_CHECKOUT
|
||||
|
||||
define GIT_HOOK_POST_GENERIC
|
||||
#!/usr/bin/env bash
|
||||
printf "\n executing $$0 git hook\n\n"
|
||||
rm -f config-generated.go
|
||||
endef
|
||||
export GIT_HOOK_POST_GENERIC
|
||||
|
||||
# This git-hook is executed after every checkout git operation
|
||||
.git/hooks/post-checkout: Makefile
|
||||
@ mkdir -p .git/hooks/
|
||||
$(QUIET_INST)echo "$$GIT_HOOK_POST_CHECKOUT" >$@
|
||||
@ chmod +x $@
|
||||
|
||||
# This git-hook is executed after every commit, merge, amend or rebase git
|
||||
# operation
|
||||
.git/hooks/post-commit .git/hooks/post-merge .git/hooks/post-rewrite: Makefile
|
||||
@ mkdir -p .git/hooks/
|
||||
$(QUIET_INST)echo "$$GIT_HOOK_POST_GENERIC" >$@
|
||||
@ chmod +x $@
|
||||
|
||||
install-git-hooks: .git/hooks/post-checkout .git/hooks/post-commit \
|
||||
.git/hooks/post-merge .git/hooks/post-rewrite
|
1
cli/VERSION
Normal file
1
cli/VERSION
Normal file
@ -0,0 +1 @@
|
||||
0.1.0
|
27
cli/arch/amd64-options.mk
Normal file
27
cli/arch/amd64-options.mk
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright (c) 2018 Intel Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Intel x86-64 settings
|
||||
|
||||
MACHINETYPE := pc
|
||||
KERNELPARAMS :=
|
||||
MACHINEACCELERATORS :=
|
||||
|
||||
# The CentOS/RHEL hypervisor binary is not called qemu-lite
|
||||
ifeq (,$(filter-out centos rhel,$(distro)))
|
||||
QEMUCMD := qemu-system-x86_64
|
||||
else
|
||||
QEMUCMD := qemu-lite-system-x86_64
|
||||
endif
|
||||
|
21
cli/arch/arm64-options.mk
Normal file
21
cli/arch/arm64-options.mk
Normal file
@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2018 Intel Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
# ARM 64 settings
|
||||
|
||||
MACHINETYPE := virt
|
||||
KERNELPARAMS :=
|
||||
MACHINEACCELERATORS :=
|
||||
|
||||
QEMUCMD := qemu-system-aarch64
|
511
cli/config.go
Normal file
511
cli/config.go
Normal file
@ -0,0 +1,511 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
goruntime "runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHypervisor = vc.QemuHypervisor
|
||||
defaultProxy = vc.KataProxyType
|
||||
defaultShim = vc.KataShimType
|
||||
defaultAgent = vc.KataContainersAgent
|
||||
)
|
||||
|
||||
// The TOML configuration file contains a number of sections (or
|
||||
// tables). The names of these tables are in dotted ("nested table")
|
||||
// form:
|
||||
//
|
||||
// [<component>.<type>]
|
||||
//
|
||||
// The components are hypervisor, proxy, shim and agent. For example,
|
||||
//
|
||||
// [proxy.kata]
|
||||
//
|
||||
// The currently supported types are listed below:
|
||||
const (
|
||||
// supported hypervisor component types
|
||||
qemuHypervisorTableType = "qemu"
|
||||
|
||||
// supported proxy component types
|
||||
ccProxyTableType = "cc"
|
||||
kataProxyTableType = "kata"
|
||||
|
||||
// supported shim component types
|
||||
ccShimTableType = "cc"
|
||||
kataShimTableType = "kata"
|
||||
|
||||
// supported agent component types
|
||||
hyperstartAgentTableType = "hyperstart"
|
||||
kataAgentTableType = "kata"
|
||||
|
||||
// the maximum amount of PCI bridges that can be cold plugged in a VM
|
||||
maxPCIBridges uint32 = 5
|
||||
)
|
||||
|
||||
type tomlConfig struct {
|
||||
Hypervisor map[string]hypervisor
|
||||
Proxy map[string]proxy
|
||||
Shim map[string]shim
|
||||
Agent map[string]agent
|
||||
Runtime runtime
|
||||
}
|
||||
|
||||
type hypervisor struct {
|
||||
Path string `toml:"path"`
|
||||
Kernel string `toml:"kernel"`
|
||||
Image string `toml:"image"`
|
||||
Firmware string `toml:"firmware"`
|
||||
MachineAccelerators string `toml:"machine_accelerators"`
|
||||
KernelParams string `toml:"kernel_params"`
|
||||
MachineType string `toml:"machine_type"`
|
||||
DefaultVCPUs int32 `toml:"default_vcpus"`
|
||||
DefaultMemSz uint32 `toml:"default_memory"`
|
||||
DefaultBridges uint32 `toml:"default_bridges"`
|
||||
DisableBlockDeviceUse bool `toml:"disable_block_device_use"`
|
||||
BlockDeviceDriver string `toml:"block_device_driver"`
|
||||
MemPrealloc bool `toml:"enable_mem_prealloc"`
|
||||
HugePages bool `toml:"enable_hugepages"`
|
||||
Swap bool `toml:"enable_swap"`
|
||||
Debug bool `toml:"enable_debug"`
|
||||
DisableNestingChecks bool `toml:"disable_nesting_checks"`
|
||||
}
|
||||
|
||||
type proxy struct {
|
||||
Path string `toml:"path"`
|
||||
Debug bool `toml:"enable_debug"`
|
||||
}
|
||||
|
||||
type runtime struct {
|
||||
Debug bool `toml:"enable_debug"`
|
||||
InterNetworkModel string `toml:"internetworking_model"`
|
||||
}
|
||||
|
||||
type shim struct {
|
||||
Path string `toml:"path"`
|
||||
Debug bool `toml:"enable_debug"`
|
||||
}
|
||||
|
||||
type agent struct {
|
||||
}
|
||||
|
||||
func (h hypervisor) path() (string, error) {
|
||||
p := h.Path
|
||||
|
||||
if h.Path == "" {
|
||||
p = defaultHypervisorPath
|
||||
}
|
||||
|
||||
return resolvePath(p)
|
||||
}
|
||||
|
||||
func (h hypervisor) kernel() (string, error) {
|
||||
p := h.Kernel
|
||||
|
||||
if p == "" {
|
||||
p = defaultKernelPath
|
||||
}
|
||||
|
||||
return resolvePath(p)
|
||||
}
|
||||
|
||||
func (h hypervisor) image() (string, error) {
|
||||
p := h.Image
|
||||
|
||||
if p == "" {
|
||||
p = defaultImagePath
|
||||
}
|
||||
|
||||
return resolvePath(p)
|
||||
}
|
||||
|
||||
func (h hypervisor) firmware() (string, error) {
|
||||
p := h.Firmware
|
||||
|
||||
if p == "" {
|
||||
if defaultFirmwarePath == "" {
|
||||
return "", nil
|
||||
}
|
||||
p = defaultFirmwarePath
|
||||
}
|
||||
|
||||
return resolvePath(p)
|
||||
}
|
||||
|
||||
func (h hypervisor) machineAccelerators() string {
|
||||
var machineAccelerators string
|
||||
accelerators := strings.Split(h.MachineAccelerators, ",")
|
||||
acceleratorsLen := len(accelerators)
|
||||
for i := 0; i < acceleratorsLen; i++ {
|
||||
if accelerators[i] != "" {
|
||||
machineAccelerators += strings.Trim(accelerators[i], "\r\t\n ") + ","
|
||||
}
|
||||
}
|
||||
|
||||
machineAccelerators = strings.Trim(machineAccelerators, ",")
|
||||
|
||||
return machineAccelerators
|
||||
}
|
||||
|
||||
func (h hypervisor) kernelParams() string {
|
||||
if h.KernelParams == "" {
|
||||
return defaultKernelParams
|
||||
}
|
||||
|
||||
return h.KernelParams
|
||||
}
|
||||
|
||||
func (h hypervisor) machineType() string {
|
||||
if h.MachineType == "" {
|
||||
return defaultMachineType
|
||||
}
|
||||
|
||||
return h.MachineType
|
||||
}
|
||||
|
||||
func (h hypervisor) defaultVCPUs() uint32 {
|
||||
numCPUs := goruntime.NumCPU()
|
||||
|
||||
if h.DefaultVCPUs < 0 || h.DefaultVCPUs > int32(numCPUs) {
|
||||
return uint32(numCPUs)
|
||||
}
|
||||
if h.DefaultVCPUs == 0 { // or unspecified
|
||||
return defaultVCPUCount
|
||||
}
|
||||
|
||||
return uint32(h.DefaultVCPUs)
|
||||
}
|
||||
|
||||
func (h hypervisor) defaultMemSz() uint32 {
|
||||
if h.DefaultMemSz < 8 {
|
||||
return defaultMemSize // MiB
|
||||
}
|
||||
|
||||
return h.DefaultMemSz
|
||||
}
|
||||
|
||||
func (h hypervisor) defaultBridges() uint32 {
|
||||
if h.DefaultBridges == 0 {
|
||||
return defaultBridgesCount
|
||||
}
|
||||
|
||||
if h.DefaultBridges > maxPCIBridges {
|
||||
return maxPCIBridges
|
||||
}
|
||||
|
||||
return h.DefaultBridges
|
||||
}
|
||||
|
||||
func (h hypervisor) blockDeviceDriver() (string, error) {
|
||||
if h.BlockDeviceDriver == "" {
|
||||
return defaultBlockDeviceDriver, nil
|
||||
}
|
||||
|
||||
if h.BlockDeviceDriver != vc.VirtioSCSI && h.BlockDeviceDriver != vc.VirtioBlock {
|
||||
return "", fmt.Errorf("Invalid value %s provided for hypervisor block storage driver, can be either %s or %s", h.BlockDeviceDriver, vc.VirtioSCSI, vc.VirtioBlock)
|
||||
}
|
||||
|
||||
return h.BlockDeviceDriver, nil
|
||||
}
|
||||
|
||||
func (p proxy) path() string {
|
||||
if p.Path == "" {
|
||||
return defaultProxyPath
|
||||
}
|
||||
|
||||
return p.Path
|
||||
}
|
||||
|
||||
func (p proxy) debug() bool {
|
||||
return p.Debug
|
||||
}
|
||||
|
||||
func (s shim) path() (string, error) {
|
||||
p := s.Path
|
||||
|
||||
if p == "" {
|
||||
p = defaultShimPath
|
||||
}
|
||||
|
||||
return resolvePath(p)
|
||||
}
|
||||
|
||||
func (s shim) debug() bool {
|
||||
return s.Debug
|
||||
}
|
||||
|
||||
func newQemuHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) {
|
||||
hypervisor, err := h.path()
|
||||
if err != nil {
|
||||
return vc.HypervisorConfig{}, err
|
||||
}
|
||||
|
||||
kernel, err := h.kernel()
|
||||
if err != nil {
|
||||
return vc.HypervisorConfig{}, err
|
||||
}
|
||||
|
||||
image, err := h.image()
|
||||
if err != nil {
|
||||
return vc.HypervisorConfig{}, err
|
||||
}
|
||||
|
||||
firmware, err := h.firmware()
|
||||
if err != nil {
|
||||
return vc.HypervisorConfig{}, err
|
||||
}
|
||||
|
||||
machineAccelerators := h.machineAccelerators()
|
||||
kernelParams := h.kernelParams()
|
||||
machineType := h.machineType()
|
||||
|
||||
blockDriver, err := h.blockDeviceDriver()
|
||||
if err != nil {
|
||||
return vc.HypervisorConfig{}, err
|
||||
}
|
||||
|
||||
return vc.HypervisorConfig{
|
||||
HypervisorPath: hypervisor,
|
||||
KernelPath: kernel,
|
||||
ImagePath: image,
|
||||
FirmwarePath: firmware,
|
||||
MachineAccelerators: machineAccelerators,
|
||||
KernelParams: vc.DeserializeParams(strings.Fields(kernelParams)),
|
||||
HypervisorMachineType: machineType,
|
||||
DefaultVCPUs: h.defaultVCPUs(),
|
||||
DefaultMemSz: h.defaultMemSz(),
|
||||
DefaultBridges: h.defaultBridges(),
|
||||
DisableBlockDeviceUse: h.DisableBlockDeviceUse,
|
||||
MemPrealloc: h.MemPrealloc,
|
||||
HugePages: h.HugePages,
|
||||
Mlock: !h.Swap,
|
||||
Debug: h.Debug,
|
||||
DisableNestingChecks: h.DisableNestingChecks,
|
||||
BlockDeviceDriver: blockDriver,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newShimConfig(s shim) (vc.ShimConfig, error) {
|
||||
path, err := s.path()
|
||||
if err != nil {
|
||||
return vc.ShimConfig{}, err
|
||||
}
|
||||
|
||||
return vc.ShimConfig{
|
||||
Path: path,
|
||||
Debug: s.debug(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func updateRuntimeConfig(configPath string, tomlConf tomlConfig, config *oci.RuntimeConfig) error {
|
||||
for k, hypervisor := range tomlConf.Hypervisor {
|
||||
switch k {
|
||||
case qemuHypervisorTableType:
|
||||
hConfig, err := newQemuHypervisorConfig(hypervisor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", configPath, err)
|
||||
}
|
||||
|
||||
config.VMConfig.Memory = uint(hConfig.DefaultMemSz)
|
||||
|
||||
config.HypervisorConfig = hConfig
|
||||
}
|
||||
}
|
||||
|
||||
for k, proxy := range tomlConf.Proxy {
|
||||
switch k {
|
||||
case ccProxyTableType:
|
||||
config.ProxyType = vc.CCProxyType
|
||||
case kataProxyTableType:
|
||||
config.ProxyType = vc.KataProxyType
|
||||
}
|
||||
|
||||
config.ProxyConfig = vc.ProxyConfig{
|
||||
Path: proxy.path(),
|
||||
Debug: proxy.debug(),
|
||||
}
|
||||
}
|
||||
|
||||
for k := range tomlConf.Agent {
|
||||
switch k {
|
||||
case hyperstartAgentTableType:
|
||||
config.AgentType = hyperstartAgentTableType
|
||||
config.AgentConfig = vc.HyperConfig{}
|
||||
|
||||
case kataAgentTableType:
|
||||
config.AgentType = kataAgentTableType
|
||||
config.AgentConfig = vc.KataAgentConfig{}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for k, shim := range tomlConf.Shim {
|
||||
switch k {
|
||||
case ccShimTableType:
|
||||
config.ShimType = vc.CCShimType
|
||||
case kataShimTableType:
|
||||
config.ShimType = vc.KataShimType
|
||||
}
|
||||
|
||||
shConfig, err := newShimConfig(shim)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", configPath, err)
|
||||
}
|
||||
|
||||
config.ShimConfig = shConfig
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadConfiguration loads the configuration file and converts it into a
|
||||
// runtime configuration.
|
||||
//
|
||||
// If ignoreLogging is true, the system logger will not be initialised nor
|
||||
// will this function make any log calls.
|
||||
//
|
||||
// All paths are resolved fully meaning if this function does not return an
|
||||
// error, all paths are valid at the time of the call.
|
||||
func loadConfiguration(configPath string, ignoreLogging bool) (resolvedConfigPath string, config oci.RuntimeConfig, err error) {
|
||||
defaultHypervisorConfig := vc.HypervisorConfig{
|
||||
HypervisorPath: defaultHypervisorPath,
|
||||
KernelPath: defaultKernelPath,
|
||||
ImagePath: defaultImagePath,
|
||||
FirmwarePath: defaultFirmwarePath,
|
||||
MachineAccelerators: defaultMachineAccelerators,
|
||||
HypervisorMachineType: defaultMachineType,
|
||||
DefaultVCPUs: defaultVCPUCount,
|
||||
DefaultMemSz: defaultMemSize,
|
||||
DefaultBridges: defaultBridgesCount,
|
||||
MemPrealloc: defaultEnableMemPrealloc,
|
||||
HugePages: defaultEnableHugePages,
|
||||
Mlock: !defaultEnableSwap,
|
||||
Debug: defaultEnableDebug,
|
||||
DisableNestingChecks: defaultDisableNestingChecks,
|
||||
BlockDeviceDriver: defaultBlockDeviceDriver,
|
||||
}
|
||||
|
||||
err = config.InterNetworkModel.SetModel(defaultInterNetworkingModel)
|
||||
if err != nil {
|
||||
return "", config, err
|
||||
}
|
||||
|
||||
defaultAgentConfig := vc.HyperConfig{}
|
||||
|
||||
config = oci.RuntimeConfig{
|
||||
HypervisorType: defaultHypervisor,
|
||||
HypervisorConfig: defaultHypervisorConfig,
|
||||
AgentType: defaultAgent,
|
||||
AgentConfig: defaultAgentConfig,
|
||||
ProxyType: defaultProxy,
|
||||
ShimType: defaultShim,
|
||||
}
|
||||
|
||||
var resolved string
|
||||
|
||||
if configPath == "" {
|
||||
resolved, err = getDefaultConfigFile()
|
||||
} else {
|
||||
resolved, err = resolvePath(configPath)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", config, fmt.Errorf("Cannot find usable config file (%v)", err)
|
||||
}
|
||||
|
||||
configData, err := ioutil.ReadFile(resolved)
|
||||
if err != nil {
|
||||
return "", config, err
|
||||
}
|
||||
|
||||
var tomlConf tomlConfig
|
||||
_, err = toml.Decode(string(configData), &tomlConf)
|
||||
if err != nil {
|
||||
return "", config, err
|
||||
}
|
||||
|
||||
if tomlConf.Runtime.Debug {
|
||||
crashOnError = true
|
||||
} else {
|
||||
// If debug is not required, switch back to the original
|
||||
// default log priority, otherwise continue in debug mode.
|
||||
kataLog.Logger.Level = originalLoggerLevel
|
||||
}
|
||||
|
||||
if tomlConf.Runtime.InterNetworkModel != "" {
|
||||
err = config.InterNetworkModel.SetModel(tomlConf.Runtime.InterNetworkModel)
|
||||
if err != nil {
|
||||
return "", config, err
|
||||
}
|
||||
}
|
||||
|
||||
if !ignoreLogging {
|
||||
err = handleSystemLog("", "")
|
||||
if err != nil {
|
||||
return "", config, err
|
||||
}
|
||||
|
||||
kataLog.WithFields(
|
||||
logrus.Fields{
|
||||
"format": "TOML",
|
||||
}).Debugf("loaded configuration")
|
||||
}
|
||||
|
||||
if err := updateRuntimeConfig(resolved, tomlConf, &config); err != nil {
|
||||
return "", config, err
|
||||
}
|
||||
|
||||
return resolved, config, nil
|
||||
}
|
||||
|
||||
// getDefaultConfigFilePaths returns a list of paths that will be
|
||||
// considered as configuration files in priority order.
|
||||
func getDefaultConfigFilePaths() []string {
|
||||
return []string{
|
||||
// normally below "/etc"
|
||||
defaultSysConfRuntimeConfiguration,
|
||||
|
||||
// normally below "/usr/share"
|
||||
defaultRuntimeConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
// getDefaultConfigFile looks in multiple default locations for a
|
||||
// configuration file and returns the resolved path for the first file
|
||||
// found, or an error if no config files can be found.
|
||||
func getDefaultConfigFile() (string, error) {
|
||||
var errs []string
|
||||
|
||||
for _, file := range getDefaultConfigFilePaths() {
|
||||
resolved, err := resolvePath(file)
|
||||
if err == nil {
|
||||
return resolved, nil
|
||||
}
|
||||
s := fmt.Sprintf("config file %q unresolvable: %v", file, err)
|
||||
errs = append(errs, s)
|
||||
}
|
||||
|
||||
return "", errors.New(strings.Join(errs, ", "))
|
||||
}
|
142
cli/config/configuration.toml.in
Normal file
142
cli/config/configuration.toml.in
Normal file
@ -0,0 +1,142 @@
|
||||
# XXX: WARNING: this file is auto-generated.
|
||||
# XXX:
|
||||
# XXX: Source file: "@CONFIG_IN@"
|
||||
# XXX: Project:
|
||||
# XXX: Name: @PROJECT_NAME@
|
||||
# XXX: Type: @PROJECT_TYPE@
|
||||
|
||||
[hypervisor.qemu]
|
||||
path = "@QEMUPATH@"
|
||||
kernel = "@KERNELPATH@"
|
||||
image = "@IMAGEPATH@"
|
||||
machine_type = "@MACHINETYPE@"
|
||||
|
||||
# Optional space-separated list of options to pass to the guest kernel.
|
||||
# For example, use `kernel_params = "vsyscall=emulate"` if you are having
|
||||
# trouble running pre-2.15 glibc.
|
||||
#
|
||||
# WARNING: - any parameter specified here will take priority over the default
|
||||
# parameter value of the same name used to start the virtual machine.
|
||||
# Do not set values here unless you understand the impact of doing so as you
|
||||
# may stop the virtual machine from booting.
|
||||
# To see the list of default parameters, enable hypervisor debug, create a
|
||||
# container and look for 'default-kernel-parameters' log entries.
|
||||
kernel_params = "@KERNELPARAMS@"
|
||||
|
||||
# Path to the firmware.
|
||||
# If you want that qemu uses the default firmware leave this option empty
|
||||
firmware = "@FIRMWAREPATH@"
|
||||
|
||||
# Machine accelerators
|
||||
# comma-separated list of machine accelerators to pass to the hypervisor.
|
||||
# For example, `machine_accelerators = "nosmm,nosmbus,nosata,nopit,static-prt,nofw"`
|
||||
machine_accelerators="@MACHINEACCELERATORS@"
|
||||
|
||||
# Default number of vCPUs per POD/VM:
|
||||
# unspecified or 0 --> will be set to @DEFVCPUS@
|
||||
# < 0 --> will be set to the actual number of physical cores
|
||||
# > 0 <= number of physical cores --> will be set to the specified number
|
||||
# > number of physical cores --> will be set to the actual number of physical cores
|
||||
default_vcpus = 1
|
||||
|
||||
|
||||
# Bridges can be used to hot plug devices.
|
||||
# Limitations:
|
||||
# * Currently only pci bridges are supported
|
||||
# * Until 30 devices per bridge can be hot plugged.
|
||||
# * Until 5 PCI bridges can be cold plugged per VM.
|
||||
# This limitation could be a bug in qemu or in the kernel
|
||||
# Default number of bridges per POD/VM:
|
||||
# unspecified or 0 --> will be set to @DEFBRIDGES@
|
||||
# > 1 <= 5 --> will be set to the specified number
|
||||
# > 5 --> will be set to 5
|
||||
default_bridges = @DEFBRIDGES@
|
||||
|
||||
# Default memory size in MiB for POD/VM.
|
||||
# If unspecified then it will be set @DEFMEMSZ@ MiB.
|
||||
#default_memory = @DEFMEMSZ@
|
||||
|
||||
# Disable block device from being used for a container's rootfs.
|
||||
# In case of a storage driver like devicemapper where a container's
|
||||
# root file system is backed by a block device, the block device is passed
|
||||
# directly to the hypervisor for performance reasons.
|
||||
# This flag prevents the block device from being passed to the hypervisor,
|
||||
# 9pfs is used instead to pass the rootfs.
|
||||
disable_block_device_use = @DEFDISABLEBLOCK@
|
||||
|
||||
# Block storage driver to be used for the hypervisor in case the container
|
||||
# rootfs is backed by a block device. This is either virtio-scsi or
|
||||
# virtio-blk.
|
||||
block_device_driver = "@DEFBLOCKSTORAGEDRIVER@"
|
||||
|
||||
# Enable pre allocation of VM RAM, default false
|
||||
# Enabling this will result in lower container density
|
||||
# as all of the memory will be allocated and locked
|
||||
# This is useful when you want to reserve all the memory
|
||||
# upfront or in the cases where you want memory latencies
|
||||
# to be very predictable
|
||||
# Default false
|
||||
#enable_mem_prealloc = true
|
||||
|
||||
# Enable huge pages for VM RAM, default false
|
||||
# Enabling this will result in the VM memory
|
||||
# being allocated using huge pages.
|
||||
# This is useful when you want to use vhost-user network
|
||||
# stacks within the container. This will automatically
|
||||
# result in memory pre allocation
|
||||
#enable_hugepages = true
|
||||
|
||||
# Enable swap of vm memory. Default false.
|
||||
# The behaviour is undefined if mem_prealloc is also set to true
|
||||
#enable_swap = true
|
||||
|
||||
# This option changes the default hypervisor and kernel parameters
|
||||
# to enable debug output where available. This extra output is added
|
||||
# to the proxy logs, but only when proxy debug is also enabled.
|
||||
#
|
||||
# Default false
|
||||
#enable_debug = true
|
||||
|
||||
# Disable the customizations done in the runtime when it detects
|
||||
# that it is running on top a VMM. This will result in the runtime
|
||||
# behaving as it would when running on bare metal.
|
||||
#
|
||||
#disable_nesting_checks = true
|
||||
|
||||
[proxy.@PROJECT_TYPE@]
|
||||
path = "@PROXYPATH@"
|
||||
|
||||
# If enabled, proxy messages will be sent to the system log
|
||||
# (default: disabled)
|
||||
#enable_debug = true
|
||||
|
||||
[shim.@PROJECT_TYPE@]
|
||||
path = "@SHIMPATH@"
|
||||
|
||||
# If enabled, shim messages will be sent to the system log
|
||||
# (default: disabled)
|
||||
#enable_debug = true
|
||||
|
||||
[agent.@PROJECT_TYPE@]
|
||||
# There is no field for this section. The goal is only to be able to
|
||||
# specify which type of agent the user wants to use.
|
||||
|
||||
[runtime]
|
||||
# If enabled, the runtime will log additional debug messages to the
|
||||
# system log
|
||||
# (default: disabled)
|
||||
#enable_debug = true
|
||||
#
|
||||
# Internetworking model
|
||||
# Determines how the VM should be connected to the
|
||||
# the container network interface
|
||||
# Options:
|
||||
#
|
||||
# - bridged
|
||||
# Uses a linux bridge to interconnect the container interface to
|
||||
# the VM. Works for most cases except macvlan and ipvlan.
|
||||
#
|
||||
# - macvtap
|
||||
# Used when the Container network interface can be bridged using
|
||||
# macvtap.
|
||||
internetworking_model="@DEFNETWORKMODEL@"
|
1054
cli/config_test.go
Normal file
1054
cli/config_test.go
Normal file
File diff suppressed because it is too large
Load Diff
147
cli/console.go
Normal file
147
cli/console.go
Normal file
@ -0,0 +1,147 @@
|
||||
// Copyright (c) 2014,2015,2016 Docker, Inc.
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var ptmxPath = "/dev/ptmx"
|
||||
|
||||
// Console represents a pseudo TTY.
|
||||
type Console struct {
|
||||
io.ReadWriteCloser
|
||||
|
||||
master *os.File
|
||||
slavePath string
|
||||
}
|
||||
|
||||
// isTerminal returns true if fd is a terminal, else false
|
||||
func isTerminal(fd uintptr) bool {
|
||||
var termios syscall.Termios
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TCGETS, uintptr(unsafe.Pointer(&termios)))
|
||||
return err == 0
|
||||
}
|
||||
|
||||
// ConsoleFromFile creates a console from a file
|
||||
func ConsoleFromFile(f *os.File) *Console {
|
||||
return &Console{
|
||||
master: f,
|
||||
}
|
||||
}
|
||||
|
||||
// NewConsole returns an initialized console that can be used within a container by copying bytes
|
||||
// from the master side to the slave that is attached as the tty for the container's init process.
|
||||
func newConsole() (*Console, error) {
|
||||
master, err := os.OpenFile(ptmxPath, unix.O_RDWR|unix.O_NOCTTY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := saneTerminal(master); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
console, err := ptsname(master)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := unlockpt(master); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Console{
|
||||
slavePath: console,
|
||||
master: master,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// File returns master
|
||||
func (c *Console) File() *os.File {
|
||||
return c.master
|
||||
}
|
||||
|
||||
// Path to slave
|
||||
func (c *Console) Path() string {
|
||||
return c.slavePath
|
||||
}
|
||||
|
||||
// Read from master
|
||||
func (c *Console) Read(b []byte) (int, error) {
|
||||
return c.master.Read(b)
|
||||
}
|
||||
|
||||
// Write to master
|
||||
func (c *Console) Write(b []byte) (int, error) {
|
||||
return c.master.Write(b)
|
||||
}
|
||||
|
||||
// Close master
|
||||
func (c *Console) Close() error {
|
||||
if m := c.master; m != nil {
|
||||
return m.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ioctl(fd uintptr, flag, data uintptr) error {
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||
// unlockpt should be called before opening the slave side of a pty.
|
||||
func unlockpt(f *os.File) error {
|
||||
var u int32
|
||||
return ioctl(f.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
|
||||
}
|
||||
|
||||
// ptsname retrieves the name of the first available pts for the given master.
|
||||
func ptsname(f *os.File) (string, error) {
|
||||
var n int32
|
||||
if err := ioctl(f.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("/dev/pts/%d", n), nil
|
||||
}
|
||||
|
||||
// saneTerminal sets the necessary tty_ioctl(4)s to ensure that a pty pair
|
||||
// created by us acts normally. In particular, a not-very-well-known default of
|
||||
// Linux unix98 ptys is that they have +onlcr by default. While this isn't a
|
||||
// problem for terminal emulators, because we relay data from the terminal we
|
||||
// also relay that funky line discipline.
|
||||
func saneTerminal(terminal *os.File) error {
|
||||
// Go doesn't have a wrapper for any of the termios ioctls.
|
||||
var termios unix.Termios
|
||||
|
||||
if err := ioctl(terminal.Fd(), unix.TCGETS, uintptr(unsafe.Pointer(&termios))); err != nil {
|
||||
return fmt.Errorf("ioctl(tty, tcgets): %s", err.Error())
|
||||
}
|
||||
|
||||
// Set -onlcr so we don't have to deal with \r.
|
||||
termios.Oflag &^= unix.ONLCR
|
||||
|
||||
if err := ioctl(terminal.Fd(), unix.TCSETS, uintptr(unsafe.Pointer(&termios))); err != nil {
|
||||
return fmt.Errorf("ioctl(tty, tcsets): %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
131
cli/console_test.go
Normal file
131
cli/console_test.go
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestConsoleFromFile(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
console := ConsoleFromFile(os.Stdout)
|
||||
|
||||
assert.NotNil(console.File(), "console file is nil")
|
||||
}
|
||||
|
||||
func TestNewConsole(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
console, err := newConsole()
|
||||
assert.NoError(err, "failed to create a new console: %s", err)
|
||||
defer console.Close()
|
||||
|
||||
assert.NotEmpty(console.Path(), "console path is empty")
|
||||
|
||||
assert.NotNil(console.File(), "console file is nil")
|
||||
}
|
||||
|
||||
func TestIsTerminal(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var fd uintptr = 4
|
||||
assert.False(isTerminal(fd), "Fd %d is not a terminal", fd)
|
||||
|
||||
console, err := newConsole()
|
||||
assert.NoError(err, "failed to create a new console: %s", err)
|
||||
defer console.Close()
|
||||
|
||||
fd = console.File().Fd()
|
||||
assert.True(isTerminal(fd), "Fd %d is a terminal", fd)
|
||||
}
|
||||
|
||||
func TestReadWrite(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// write operation
|
||||
f, err := ioutil.TempFile(os.TempDir(), ".tty")
|
||||
assert.NoError(err, "failed to create a temporal file")
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
console := ConsoleFromFile(f)
|
||||
assert.NotNil(console)
|
||||
defer console.Close()
|
||||
|
||||
msgWrite := "hello"
|
||||
l, err := console.Write([]byte(msgWrite))
|
||||
assert.NoError(err, "failed to write message: %s", msgWrite)
|
||||
assert.Equal(len(msgWrite), l)
|
||||
|
||||
console.master.Sync()
|
||||
console.master.Seek(0, 0)
|
||||
|
||||
// Read operation
|
||||
msgRead := make([]byte, len(msgWrite))
|
||||
l, err = console.Read(msgRead)
|
||||
assert.NoError(err, "failed to read message: %s", msgWrite)
|
||||
assert.Equal(len(msgWrite), l)
|
||||
assert.Equal(msgWrite, string(msgRead))
|
||||
}
|
||||
|
||||
func TestNewConsoleFail(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
orgPtmxPath := ptmxPath
|
||||
defer func() { ptmxPath = orgPtmxPath }()
|
||||
|
||||
// OpenFile failure
|
||||
ptmxPath = "/this/file/does/not/exist"
|
||||
c, err := newConsole()
|
||||
assert.Error(err)
|
||||
assert.Nil(c)
|
||||
|
||||
// saneTerminal failure
|
||||
f, err := ioutil.TempFile("", "")
|
||||
assert.NoError(err)
|
||||
assert.NoError(f.Close())
|
||||
defer os.Remove(f.Name())
|
||||
ptmxPath = f.Name()
|
||||
c, err = newConsole()
|
||||
assert.Error(err)
|
||||
assert.Nil(c)
|
||||
}
|
||||
|
||||
func TestConsoleClose(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// nil master
|
||||
c := &Console{}
|
||||
assert.NoError(c.Close())
|
||||
|
||||
f, err := ioutil.TempFile("", "")
|
||||
assert.NoError(err)
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
c.master = f
|
||||
assert.NoError(c.Close())
|
||||
}
|
||||
|
||||
func TestConsolePtsnameFail(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pts, err := ptsname(nil)
|
||||
assert.Error(err)
|
||||
assert.Empty(pts)
|
||||
}
|
382
cli/create.go
Normal file
382
cli/create.go
Normal file
@ -0,0 +1,382 @@
|
||||
// Copyright (c) 2014,2015,2016 Docker, Inc.
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var createCLICommand = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a container",
|
||||
ArgsUsage: `<container-id>
|
||||
|
||||
<container-id> is your name for the instance of the container that you
|
||||
are starting. The name you provide for the container instance must be unique
|
||||
on your host.`,
|
||||
Description: `The create command creates an instance of a container for a bundle. The
|
||||
bundle is a directory with a specification file named "` + specConfig + `" and a
|
||||
root filesystem.
|
||||
The specification file includes an args parameter. The args parameter is
|
||||
used to specify command(s) that get run when the container is started.
|
||||
To change the command(s) that get executed on start, edit the args
|
||||
parameter of the spec.`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "bundle, b",
|
||||
Value: "",
|
||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "console",
|
||||
Value: "",
|
||||
Usage: "path to a pseudo terminal",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "console-socket",
|
||||
Value: "",
|
||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Value: "",
|
||||
Usage: "specify the file to write the process id to",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
runtimeConfig, ok := context.App.Metadata["runtimeConfig"].(oci.RuntimeConfig)
|
||||
if !ok {
|
||||
return errors.New("invalid runtime config")
|
||||
}
|
||||
|
||||
console, err := setupConsole(context.String("console"), context.String("console-socket"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return create(context.Args().First(),
|
||||
context.String("bundle"),
|
||||
console,
|
||||
context.String("pid-file"),
|
||||
true,
|
||||
runtimeConfig,
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
// Use a variable to allow tests to modify its value
|
||||
var getKernelParamsFunc = getKernelParams
|
||||
|
||||
func create(containerID, bundlePath, console, pidFilePath string, detach bool,
|
||||
runtimeConfig oci.RuntimeConfig) error {
|
||||
var err error
|
||||
|
||||
// Checks the MUST and MUST NOT from OCI runtime specification
|
||||
if bundlePath, err = validCreateParams(containerID, bundlePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ociSpec, err := oci.ParseConfigJSON(bundlePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containerType, err := ociSpec.ContainerType()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
disableOutput := noNeedForOutput(detach, ociSpec.Process.Terminal)
|
||||
|
||||
var process vc.Process
|
||||
|
||||
switch containerType {
|
||||
case vc.PodSandbox:
|
||||
process, err = createPod(ociSpec, runtimeConfig, containerID, bundlePath, console, disableOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case vc.PodContainer:
|
||||
process, err = createContainer(ociSpec, containerID, bundlePath, console, disableOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// config.json provides a cgroups path that has to be used to create "tasks"
|
||||
// and "cgroups.procs" files. Those files have to be filled with a PID, which
|
||||
// is shim's in our case. This is mandatory to make sure there is no one
|
||||
// else (like Docker) trying to create those files on our behalf. We want to
|
||||
// know those files location so that we can remove them when delete is called.
|
||||
cgroupsPathList, err := processCgroupsPath(ociSpec, containerType.IsPod())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// cgroupsDirPath is CgroupsPath fetch from OCI spec
|
||||
var cgroupsDirPath string
|
||||
if ociSpec.Linux != nil {
|
||||
cgroupsDirPath = ociSpec.Linux.CgroupsPath
|
||||
}
|
||||
|
||||
if err := createCgroupsFiles(containerID, cgroupsDirPath, cgroupsPathList, process.Pid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Creation of PID file has to be the last thing done in the create
|
||||
// because containerd considers the create complete after this file
|
||||
// is created.
|
||||
return createPIDFile(pidFilePath, process.Pid)
|
||||
}
|
||||
|
||||
func getKernelParams(containerID string) []vc.Param {
|
||||
return []vc.Param{
|
||||
{
|
||||
Key: "init",
|
||||
Value: "/usr/lib/systemd/systemd",
|
||||
},
|
||||
{
|
||||
Key: "systemd.unit",
|
||||
Value: systemdUnitName,
|
||||
},
|
||||
{
|
||||
Key: "systemd.mask",
|
||||
Value: "systemd-networkd.service",
|
||||
},
|
||||
{
|
||||
Key: "systemd.mask",
|
||||
Value: "systemd-networkd.socket",
|
||||
},
|
||||
{
|
||||
Key: "ip",
|
||||
Value: fmt.Sprintf("::::::%s::off::", containerID),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// setKernelParams adds the user-specified kernel parameters (from the
|
||||
// configuration file) to the defaults so that the former take priority.
|
||||
func setKernelParams(containerID string, runtimeConfig *oci.RuntimeConfig) error {
|
||||
defaultKernelParams := getKernelParamsFunc(containerID)
|
||||
|
||||
if runtimeConfig.HypervisorConfig.Debug {
|
||||
strParams := vc.SerializeParams(defaultKernelParams, "=")
|
||||
formatted := strings.Join(strParams, " ")
|
||||
|
||||
kataLog.WithField("default-kernel-parameters", formatted).Debug()
|
||||
}
|
||||
|
||||
// retrieve the parameters specified in the config file
|
||||
userKernelParams := runtimeConfig.HypervisorConfig.KernelParams
|
||||
|
||||
// reset
|
||||
runtimeConfig.HypervisorConfig.KernelParams = []vc.Param{}
|
||||
|
||||
// first, add default values
|
||||
for _, p := range defaultKernelParams {
|
||||
if err := (runtimeConfig).AddKernelParam(p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// now re-add the user-specified values so that they take priority.
|
||||
for _, p := range userKernelParams {
|
||||
if err := (runtimeConfig).AddKernelParam(p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createPod(ociSpec oci.CompatOCISpec, runtimeConfig oci.RuntimeConfig,
|
||||
containerID, bundlePath, console string, disableOutput bool) (vc.Process, error) {
|
||||
|
||||
err := setKernelParams(containerID, &runtimeConfig)
|
||||
if err != nil {
|
||||
return vc.Process{}, err
|
||||
}
|
||||
|
||||
podConfig, err := oci.PodConfig(ociSpec, runtimeConfig, bundlePath, containerID, console, disableOutput)
|
||||
if err != nil {
|
||||
return vc.Process{}, err
|
||||
}
|
||||
|
||||
pod, err := vci.CreatePod(podConfig)
|
||||
if err != nil {
|
||||
return vc.Process{}, err
|
||||
}
|
||||
|
||||
containers := pod.GetAllContainers()
|
||||
if len(containers) != 1 {
|
||||
return vc.Process{}, fmt.Errorf("BUG: Container list from pod is wrong, expecting only one container, found %d containers", len(containers))
|
||||
}
|
||||
|
||||
return containers[0].Process(), nil
|
||||
}
|
||||
|
||||
func createContainer(ociSpec oci.CompatOCISpec, containerID, bundlePath,
|
||||
console string, disableOutput bool) (vc.Process, error) {
|
||||
|
||||
contConfig, err := oci.ContainerConfig(ociSpec, bundlePath, containerID, console, disableOutput)
|
||||
if err != nil {
|
||||
return vc.Process{}, err
|
||||
}
|
||||
|
||||
podID, err := ociSpec.PodID()
|
||||
if err != nil {
|
||||
return vc.Process{}, err
|
||||
}
|
||||
|
||||
_, c, err := vci.CreateContainer(podID, contConfig)
|
||||
if err != nil {
|
||||
return vc.Process{}, err
|
||||
}
|
||||
|
||||
return c.Process(), nil
|
||||
}
|
||||
|
||||
func createCgroupsFiles(containerID string, cgroupsDirPath string, cgroupsPathList []string, pid int) error {
|
||||
if len(cgroupsPathList) == 0 {
|
||||
fields := logrus.Fields{
|
||||
"container": containerID,
|
||||
"pid": pid,
|
||||
}
|
||||
kataLog.WithFields(fields).Info("Cgroups files not created because cgroupsPath was empty")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, cgroupsPath := range cgroupsPathList {
|
||||
if err := os.MkdirAll(cgroupsPath, cgroupsDirMode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.Contains(cgroupsPath, "cpu") && cgroupsDirPath != "" {
|
||||
parent := strings.TrimSuffix(cgroupsPath, cgroupsDirPath)
|
||||
copyParentCPUSet(cgroupsPath, parent)
|
||||
}
|
||||
|
||||
tasksFilePath := filepath.Join(cgroupsPath, cgroupsTasksFile)
|
||||
procsFilePath := filepath.Join(cgroupsPath, cgroupsProcsFile)
|
||||
|
||||
pidStr := fmt.Sprintf("%d", pid)
|
||||
|
||||
for _, path := range []string{tasksFilePath, procsFilePath} {
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, cgroupsFileMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
n, err := f.WriteString(pidStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n < len(pidStr) {
|
||||
return fmt.Errorf("Could not write pid to %q: only %d bytes written out of %d",
|
||||
path, n, len(pidStr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createPIDFile(pidFilePath string, pid int) error {
|
||||
if pidFilePath == "" {
|
||||
// runtime should not fail since pid file is optional
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(pidFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Create(pidFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
pidStr := fmt.Sprintf("%d", pid)
|
||||
|
||||
n, err := f.WriteString(pidStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n < len(pidStr) {
|
||||
return fmt.Errorf("Could not write pid to '%s': only %d bytes written out of %d", pidFilePath, n, len(pidStr))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyParentCPUSet copies the cpuset.cpus and cpuset.mems from the parent
|
||||
// directory to the current directory if the file's contents are 0
|
||||
func copyParentCPUSet(current, parent string) error {
|
||||
currentCpus, currentMems, err := getCPUSet(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parentCpus, parentMems, err := getCPUSet(parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(parentCpus) < 1 || len(parentMems) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var cgroupsFileMode = os.FileMode(0600)
|
||||
if isEmptyString(currentCpus) {
|
||||
if err := writeFile(filepath.Join(current, "cpuset.cpus"), string(parentCpus), cgroupsFileMode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if isEmptyString(currentMems) {
|
||||
if err := writeFile(filepath.Join(current, "cpuset.mems"), string(parentMems), cgroupsFileMode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCPUSet(parent string) (cpus []byte, mems []byte, err error) {
|
||||
if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if mems, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.mems")); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return cpus, mems, nil
|
||||
}
|
1197
cli/create_test.go
Normal file
1197
cli/create_test.go
Normal file
File diff suppressed because it is too large
Load Diff
155
cli/delete.go
Normal file
155
cli/delete.go
Normal file
@ -0,0 +1,155 @@
|
||||
// Copyright (c) 2014,2015,2016 Docker, Inc.
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var deleteCLICommand = cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete any resources held by one or more containers",
|
||||
ArgsUsage: `<container-id> [container-id...]
|
||||
|
||||
<container-id> is the name for the instance of the container.
|
||||
|
||||
EXAMPLE:
|
||||
If the container id is "ubuntu01" and ` + name + ` list currently shows the
|
||||
status of "ubuntu01" as "stopped" the following will delete resources held
|
||||
for "ubuntu01" removing "ubuntu01" from the ` + name + ` list of containers:
|
||||
|
||||
# ` + name + ` delete ubuntu01`,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "Forcibly deletes the container if it is still running (uses SIGKILL)",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
args := context.Args()
|
||||
if args.Present() == false {
|
||||
return fmt.Errorf("Missing container ID, should at least provide one")
|
||||
}
|
||||
|
||||
force := context.Bool("force")
|
||||
for _, cID := range []string(args) {
|
||||
if err := delete(cID, force); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func delete(containerID string, force bool) error {
|
||||
// Checks the MUST and MUST NOT from OCI runtime specification
|
||||
status, podID, err := getExistingContainerInfo(containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containerID = status.ID
|
||||
|
||||
containerType, err := oci.GetContainerType(status.Annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Retrieve OCI spec configuration.
|
||||
ociSpec, err := oci.GetOCIConfig(status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
forceStop := false
|
||||
if oci.StateToOCIState(status.State) == oci.StateRunning {
|
||||
if !force {
|
||||
return fmt.Errorf("Container still running, should be stopped")
|
||||
}
|
||||
|
||||
forceStop = true
|
||||
}
|
||||
|
||||
switch containerType {
|
||||
case vc.PodSandbox:
|
||||
if err := deletePod(podID); err != nil {
|
||||
return err
|
||||
}
|
||||
case vc.PodContainer:
|
||||
if err := deleteContainer(podID, containerID, forceStop); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("Invalid container type found")
|
||||
}
|
||||
|
||||
// In order to prevent any file descriptor leak related to cgroups files
|
||||
// that have been previously created, we have to remove them before this
|
||||
// function returns.
|
||||
cgroupsPathList, err := processCgroupsPath(ociSpec, containerType.IsPod())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return removeCgroupsPath(containerID, cgroupsPathList)
|
||||
}
|
||||
|
||||
func deletePod(podID string) error {
|
||||
if _, err := vci.StopPod(podID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := vci.DeletePod(podID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteContainer(podID, containerID string, forceStop bool) error {
|
||||
if forceStop {
|
||||
if _, err := vci.StopContainer(podID, containerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := vci.DeleteContainer(podID, containerID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeCgroupsPath(containerID string, cgroupsPathList []string) error {
|
||||
if len(cgroupsPathList) == 0 {
|
||||
kataLog.WithField("container", containerID).Info("Cgroups files not removed because cgroupsPath was empty")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, cgroupsPath := range cgroupsPathList {
|
||||
if err := os.RemoveAll(cgroupsPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
597
cli/delete_test.go
Normal file
597
cli/delete_test.go
Normal file
@ -0,0 +1,597 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func testRemoveCgroupsPathSuccessful(t *testing.T, cgroupsPathList []string) {
|
||||
if err := removeCgroupsPath("foo", cgroupsPathList); err != nil {
|
||||
t.Fatalf("This test should succeed (cgroupsPathList = %v): %s", cgroupsPathList, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveCgroupsPathEmptyPathSuccessful(t *testing.T) {
|
||||
testRemoveCgroupsPathSuccessful(t, []string{})
|
||||
}
|
||||
|
||||
func TestRemoveCgroupsPathNonEmptyPathSuccessful(t *testing.T) {
|
||||
cgroupsPath, err := ioutil.TempDir(testDir, "cgroups-path-")
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create temporary cgroups directory: %s", err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(cgroupsPath, testDirMode); err != nil {
|
||||
t.Fatalf("CgroupsPath directory %q could not be created: %s", cgroupsPath, err)
|
||||
}
|
||||
|
||||
testRemoveCgroupsPathSuccessful(t, []string{cgroupsPath})
|
||||
|
||||
if _, err := os.Stat(cgroupsPath); err == nil {
|
||||
t.Fatalf("CgroupsPath directory %q should have been removed: %s", cgroupsPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteInvalidContainer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Missing container id
|
||||
err := delete("", false)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
|
||||
// Mock Listpod error
|
||||
err = delete(testContainerID, false)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
// Container missing in ListPod
|
||||
err = delete(testContainerID, false)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestDeleteMissingContainerTypeAnnotation(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
err := delete(pod.ID(), false)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestDeleteInvalidConfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
err := delete(pod.ID(), false)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func testConfigSetup(t *testing.T) string {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
|
||||
bundlePath := filepath.Join(tmpdir, "bundle")
|
||||
err = os.MkdirAll(bundlePath, testDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
err = createOCIConfig(bundlePath)
|
||||
assert.NoError(err)
|
||||
|
||||
// config json path
|
||||
configPath := filepath.Join(bundlePath, "config.json")
|
||||
return configPath
|
||||
}
|
||||
|
||||
func TestDeletePod(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
},
|
||||
State: vc.State{
|
||||
State: "ready",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
err = delete(pod.ID(), false)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.StopPodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.StopPodFunc = nil
|
||||
}()
|
||||
|
||||
err = delete(pod.ID(), false)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.DeletePodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.DeletePodFunc = nil
|
||||
}()
|
||||
|
||||
err = delete(pod.ID(), false)
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestDeleteInvalidContainerType(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: "InvalidType",
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
},
|
||||
State: vc.State{
|
||||
State: "created",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
// Delete an invalid container type
|
||||
err = delete(pod.ID(), false)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestDeletePodRunning(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
},
|
||||
State: vc.State{
|
||||
State: "running",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
// Delete on a running pod should fail
|
||||
err = delete(pod.ID(), false)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.StopPodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.StopPodFunc = nil
|
||||
}()
|
||||
|
||||
// Force delete a running pod
|
||||
err = delete(pod.ID(), true)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.DeletePodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.DeletePodFunc = nil
|
||||
}()
|
||||
|
||||
err = delete(pod.ID(), true)
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestDeleteRunningContainer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
pod.MockContainers = []*vcMock.Container{
|
||||
{
|
||||
MockID: testContainerID,
|
||||
MockPod: pod,
|
||||
},
|
||||
}
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.MockContainers[0].ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
},
|
||||
State: vc.State{
|
||||
State: "running",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
// Delete on a running container should fail.
|
||||
err = delete(pod.MockContainers[0].ID(), false)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
|
||||
// force delete
|
||||
err = delete(pod.MockContainers[0].ID(), true)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.StopContainerFunc = testStopContainerFuncReturnNil
|
||||
defer func() {
|
||||
testingImpl.StopContainerFunc = nil
|
||||
}()
|
||||
|
||||
err = delete(pod.MockContainers[0].ID(), true)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.DeleteContainerFunc = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
return &vcMock.Container{}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.DeleteContainerFunc = nil
|
||||
}()
|
||||
|
||||
err = delete(pod.MockContainers[0].ID(), true)
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestDeleteContainer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
pod.MockContainers = []*vcMock.Container{
|
||||
{
|
||||
MockID: testContainerID,
|
||||
MockPod: pod,
|
||||
},
|
||||
}
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.MockContainers[0].ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
},
|
||||
State: vc.State{
|
||||
State: "ready",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
err = delete(pod.MockContainers[0].ID(), false)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.StopContainerFunc = testStopContainerFuncReturnNil
|
||||
defer func() {
|
||||
testingImpl.StopContainerFunc = nil
|
||||
}()
|
||||
|
||||
err = delete(pod.MockContainers[0].ID(), false)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.DeleteContainerFunc = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
return &vcMock.Container{}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.DeleteContainerFunc = nil
|
||||
}()
|
||||
|
||||
err = delete(pod.MockContainers[0].ID(), false)
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestDeleteCLIFunction(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
flagSet := &flag.FlagSet{}
|
||||
app := cli.NewApp()
|
||||
|
||||
ctx := cli.NewContext(app, flagSet, nil)
|
||||
|
||||
fn, ok := deleteCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
// no container id in the Metadata
|
||||
err := fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
|
||||
flagSet = flag.NewFlagSet("container-id", flag.ContinueOnError)
|
||||
flagSet.Parse([]string{"xyz"})
|
||||
ctx = cli.NewContext(app, flagSet, nil)
|
||||
|
||||
err = fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestDeleteCLIFunctionSuccess(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
pod.MockContainers = []*vcMock.Container{
|
||||
{
|
||||
MockID: testContainerID,
|
||||
MockPod: pod,
|
||||
},
|
||||
}
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
},
|
||||
State: vc.State{
|
||||
State: "ready",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
testingImpl.StopPodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
testingImpl.DeletePodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
testingImpl.StopPodFunc = nil
|
||||
testingImpl.DeletePodFunc = nil
|
||||
}()
|
||||
|
||||
flagSet := &flag.FlagSet{}
|
||||
app := cli.NewApp()
|
||||
|
||||
ctx := cli.NewContext(app, flagSet, nil)
|
||||
|
||||
fn, ok := deleteCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
err = fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
|
||||
flagSet = flag.NewFlagSet("container-id", flag.ContinueOnError)
|
||||
flagSet.Parse([]string{pod.ID()})
|
||||
ctx = cli.NewContext(app, flagSet, nil)
|
||||
assert.NotNil(ctx)
|
||||
|
||||
err = fn(ctx)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
func TestRemoveCGroupsPath(t *testing.T) {
|
||||
if os.Geteuid() == 0 {
|
||||
t.Skip(testDisabledNeedNonRoot)
|
||||
}
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
dir := filepath.Join(tmpdir, "dir")
|
||||
|
||||
err = os.Mkdir(dir, testDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
// make directory unreadable by non-root user
|
||||
err = os.Chmod(tmpdir, 0000)
|
||||
assert.NoError(err)
|
||||
defer func() {
|
||||
_ = os.Chmod(tmpdir, 0755)
|
||||
}()
|
||||
|
||||
err = removeCgroupsPath("foo", []string{dir})
|
||||
assert.Error(err)
|
||||
}
|
273
cli/exec.go
Normal file
273
cli/exec.go
Normal file
@ -0,0 +1,273 @@
|
||||
// Copyright (c) 2014,2015,2016 Docker, Inc.
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type execParams struct {
|
||||
ociProcess oci.CompatOCIProcess
|
||||
cID string
|
||||
pidFile string
|
||||
console string
|
||||
consoleSock string
|
||||
processLabel string
|
||||
detach bool
|
||||
noSubreaper bool
|
||||
}
|
||||
|
||||
var execCLICommand = cli.Command{
|
||||
Name: "exec",
|
||||
Usage: "Execute new process inside the container",
|
||||
ArgsUsage: `<container-id> <command> [command options] || -p process.json <container-id>
|
||||
|
||||
<container-id> is the name for the instance of the container and <command>
|
||||
is the command to be executed in the container. <command> can't be empty
|
||||
unless a "-p" flag provided.
|
||||
|
||||
EXAMPLE:
|
||||
If the container is configured to run the linux ps command the following
|
||||
will output a list of processes running in the container:
|
||||
|
||||
# ` + name + ` <container-id> ps`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "console",
|
||||
Usage: "path to a pseudo terminal",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "console-socket",
|
||||
Value: "",
|
||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cwd",
|
||||
Usage: "current working directory in the container",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "env, e",
|
||||
Usage: "set environment variables",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "tty, t",
|
||||
Usage: "allocate a pseudo-TTY",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "user, u",
|
||||
Usage: "UID (format: <uid>[:<gid>])",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "process, p",
|
||||
Usage: "path to the process.json",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detach,d",
|
||||
Usage: "detach from the container's process",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Value: "",
|
||||
Usage: "specify the file to write the process id to",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "process-label",
|
||||
Usage: "set the asm process label for the process commonly used with selinux",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "apparmor",
|
||||
Usage: "set the apparmor profile for the process",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-new-privs",
|
||||
Usage: "set the no new privileges value for the process",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "cap, c",
|
||||
Value: &cli.StringSlice{},
|
||||
Usage: "add a capability to the bounding set for the process",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-subreaper",
|
||||
Usage: "disable the use of the subreaper used to reap reparented processes",
|
||||
Hidden: true,
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
return execute(context)
|
||||
},
|
||||
}
|
||||
|
||||
func generateExecParams(context *cli.Context, specProcess *oci.CompatOCIProcess) (execParams, error) {
|
||||
ctxArgs := context.Args()
|
||||
|
||||
params := execParams{
|
||||
cID: ctxArgs.First(),
|
||||
pidFile: context.String("pid-file"),
|
||||
console: context.String("console"),
|
||||
consoleSock: context.String("console-socket"),
|
||||
detach: context.Bool("detach"),
|
||||
processLabel: context.String("process-label"),
|
||||
noSubreaper: context.Bool("no-subreaper"),
|
||||
}
|
||||
|
||||
if context.String("process") != "" {
|
||||
var ociProcess oci.CompatOCIProcess
|
||||
|
||||
fileContent, err := ioutil.ReadFile(context.String("process"))
|
||||
if err != nil {
|
||||
return execParams{}, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(fileContent, &ociProcess); err != nil {
|
||||
return execParams{}, err
|
||||
}
|
||||
|
||||
params.ociProcess = ociProcess
|
||||
} else {
|
||||
params.ociProcess = *specProcess
|
||||
|
||||
// Override terminal
|
||||
if context.IsSet("tty") {
|
||||
params.ociProcess.Terminal = context.Bool("tty")
|
||||
}
|
||||
|
||||
// Override user
|
||||
if context.String("user") != "" {
|
||||
params.ociProcess.User = specs.User{
|
||||
// This field is a Windows-only field
|
||||
// according to the specification. However, it
|
||||
// is abused here to allow the username
|
||||
// specified in the OCI runtime configuration
|
||||
// file to be overridden by a CLI request.
|
||||
Username: context.String("user"),
|
||||
}
|
||||
}
|
||||
|
||||
// Override env
|
||||
params.ociProcess.Env = append(params.ociProcess.Env, context.StringSlice("env")...)
|
||||
|
||||
// Override cwd
|
||||
if context.String("cwd") != "" {
|
||||
params.ociProcess.Cwd = context.String("cwd")
|
||||
}
|
||||
|
||||
// Override no-new-privs
|
||||
if context.IsSet("no-new-privs") {
|
||||
params.ociProcess.NoNewPrivileges = context.Bool("no-new-privs")
|
||||
}
|
||||
|
||||
// Override apparmor
|
||||
if context.String("apparmor") != "" {
|
||||
params.ociProcess.ApparmorProfile = context.String("apparmor")
|
||||
}
|
||||
|
||||
params.ociProcess.Args = ctxArgs.Tail()
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func execute(context *cli.Context) error {
|
||||
containerID := context.Args().First()
|
||||
status, podID, err := getExistingContainerInfo(containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Retrieve OCI spec configuration.
|
||||
ociSpec, err := oci.GetOCIConfig(status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params, err := generateExecParams(context, ociSpec.Process)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params.cID = status.ID
|
||||
|
||||
// container MUST be running
|
||||
if status.State.State != vc.StateRunning {
|
||||
return fmt.Errorf("Container %s is not running", params.cID)
|
||||
}
|
||||
|
||||
envVars, err := oci.EnvVars(params.ociProcess.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
consolePath, err := setupConsole(params.console, params.consoleSock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := fmt.Sprintf("%d:%d", params.ociProcess.User.UID, params.ociProcess.User.GID)
|
||||
|
||||
if params.ociProcess.User.Username != "" {
|
||||
user = params.ociProcess.User.Username
|
||||
}
|
||||
|
||||
cmd := vc.Cmd{
|
||||
Args: params.ociProcess.Args,
|
||||
Envs: envVars,
|
||||
WorkDir: params.ociProcess.Cwd,
|
||||
User: user,
|
||||
Interactive: params.ociProcess.Terminal,
|
||||
Console: consolePath,
|
||||
Detach: noNeedForOutput(params.detach, params.ociProcess.Terminal),
|
||||
}
|
||||
|
||||
_, _, process, err := vci.EnterContainer(podID, params.cID, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Creation of PID file has to be the last thing done in the exec
|
||||
// because containerd considers the exec to have finished starting
|
||||
// after this file is created.
|
||||
if err := createPIDFile(params.pidFile, process.Pid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if params.detach {
|
||||
return nil
|
||||
}
|
||||
|
||||
p, err := os.FindProcess(process.Pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ps, err := p.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Process state %s, container info %+v: %v",
|
||||
ps.String(), status, err)
|
||||
}
|
||||
|
||||
// Exit code has to be forwarded in this case.
|
||||
return cli.NewExitError("", ps.Sys().(syscall.WaitStatus).ExitStatus())
|
||||
}
|
696
cli/exec_test.go
Normal file
696
cli/exec_test.go
Normal file
@ -0,0 +1,696 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
vcAnnotations "github.com/kata-containers/runtime/virtcontainers/pkg/annotations"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/vcMock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestExecCLIFunction(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
flagSet := &flag.FlagSet{}
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, flagSet, nil)
|
||||
|
||||
fn, ok := startCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
// no container-id in the Metadata
|
||||
err := fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
|
||||
// pass container-id
|
||||
flagSet = flag.NewFlagSet("container-id", flag.ContinueOnError)
|
||||
flagSet.Parse([]string{"xyz"})
|
||||
ctx = cli.NewContext(app, flagSet, nil)
|
||||
|
||||
err = fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestExecuteErrors(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
flagSet := flag.NewFlagSet("", 0)
|
||||
ctx := cli.NewContext(cli.NewApp(), flagSet, nil)
|
||||
|
||||
// missing container id
|
||||
err := execute(ctx)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
|
||||
// ListPod error
|
||||
flagSet.Parse([]string{testContainerID})
|
||||
err = execute(ctx)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
// Config path missing in annotations
|
||||
annotations := map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, vc.State{}, vc.State{}, annotations), nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
err = execute(ctx)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
|
||||
// Container not running
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
annotations = map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, vc.State{}, vc.State{}, annotations), nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
err = execute(ctx)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestExecuteErrorReadingProcessJson(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// non-existent path
|
||||
processPath := filepath.Join(tmpdir, "process.json")
|
||||
|
||||
flagSet := flag.NewFlagSet("", 0)
|
||||
flagSet.String("process", processPath, "")
|
||||
flagSet.Parse([]string{testContainerID})
|
||||
ctx := cli.NewContext(cli.NewApp(), flagSet, nil)
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
annotations := map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
}
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, annotations), nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
// Note: flags can only be tested with the CLI command function
|
||||
fn, ok := execCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
err = fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestExecuteErrorOpeningConsole(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
consoleSock := filepath.Join(tmpdir, "console-sock")
|
||||
|
||||
flagSet := flag.NewFlagSet("", 0)
|
||||
flagSet.String("console-socket", consoleSock, "")
|
||||
flagSet.Parse([]string{testContainerID})
|
||||
ctx := cli.NewContext(cli.NewApp(), flagSet, nil)
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
annotations := map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
}
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, annotations), nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
// Note: flags can only be tested with the CLI command function
|
||||
fn, ok := execCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
err = fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func testExecParamsSetup(t *testing.T, pidFilePath, consolePath string, detach bool) *flag.FlagSet {
|
||||
flagSet := flag.NewFlagSet("", 0)
|
||||
|
||||
flagSet.String("pid-file", pidFilePath, "")
|
||||
flagSet.String("console", consolePath, "")
|
||||
flagSet.String("console-socket", "", "")
|
||||
flagSet.Bool("detach", detach, "")
|
||||
flagSet.String("process-label", "testlabel", "")
|
||||
flagSet.Bool("no-subreaper", false, "")
|
||||
|
||||
return flagSet
|
||||
}
|
||||
|
||||
func TestExecuteWithFlags(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
pidFilePath := filepath.Join(tmpdir, "pid")
|
||||
consolePath := "/dev/ptmx"
|
||||
|
||||
flagSet := testExecParamsSetup(t, pidFilePath, consolePath, false)
|
||||
flagSet.String("user", "root", "")
|
||||
flagSet.String("cwd", "/home/root", "")
|
||||
flagSet.String("apparmor", "/tmp/profile", "")
|
||||
flagSet.Bool("no-new-privs", false, "")
|
||||
|
||||
flagSet.Parse([]string{testContainerID, "/tmp/foo"})
|
||||
ctx := cli.NewContext(cli.NewApp(), flagSet, nil)
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
annotations := map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
}
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, annotations), nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
fn, ok := execCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
// EnterContainer error
|
||||
err = fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.EnterContainerFunc = func(podID, containerID string, cmd vc.Cmd) (vc.VCPod, vc.VCContainer, *vc.Process, error) {
|
||||
return &vcMock.Pod{}, &vcMock.Container{}, &vc.Process{}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.EnterContainerFunc = nil
|
||||
os.Remove(pidFilePath)
|
||||
}()
|
||||
|
||||
// Process not running error
|
||||
err = fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
|
||||
os.Remove(pidFilePath)
|
||||
|
||||
// Process ran and exited successfully
|
||||
testingImpl.EnterContainerFunc = func(podID, containerID string, cmd vc.Cmd) (vc.VCPod, vc.VCContainer, *vc.Process, error) {
|
||||
// create a fake container process
|
||||
workload := []string{"cat", "/dev/null"}
|
||||
command := exec.Command(workload[0], workload[1:]...)
|
||||
err := command.Start()
|
||||
assert.NoError(err, "Unable to start process %v: %s", workload, err)
|
||||
|
||||
vcProcess := vc.Process{}
|
||||
vcProcess.Pid = command.Process.Pid
|
||||
return &vcMock.Pod{}, &vcMock.Container{}, &vcProcess, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.EnterContainerFunc = nil
|
||||
os.Remove(pidFilePath)
|
||||
}()
|
||||
|
||||
// Should get an exit code when run in non-detached mode.
|
||||
err = fn(ctx)
|
||||
_, ok = err.(*cli.ExitError)
|
||||
assert.True(ok, true, "Exit code not received for fake workload process")
|
||||
}
|
||||
|
||||
func TestExecuteWithFlagsDetached(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
pidFilePath := filepath.Join(tmpdir, "pid")
|
||||
consolePath := "/dev/ptmx"
|
||||
detach := true
|
||||
|
||||
flagSet := testExecParamsSetup(t, pidFilePath, consolePath, detach)
|
||||
flagSet.Parse([]string{testContainerID, "/tmp/foo"})
|
||||
ctx := cli.NewContext(cli.NewApp(), flagSet, nil)
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
annotations := map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
}
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, annotations), nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
testingImpl.EnterContainerFunc = func(podID, containerID string, cmd vc.Cmd) (vc.VCPod, vc.VCContainer, *vc.Process, error) {
|
||||
// create a fake container process
|
||||
workload := []string{"cat", "/dev/null"}
|
||||
command := exec.Command(workload[0], workload[1:]...)
|
||||
err := command.Start()
|
||||
assert.NoError(err, "Unable to start process %v: %s", workload, err)
|
||||
|
||||
vcProcess := vc.Process{}
|
||||
vcProcess.Pid = command.Process.Pid
|
||||
return &vcMock.Pod{}, &vcMock.Container{}, &vcProcess, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.EnterContainerFunc = nil
|
||||
os.Remove(pidFilePath)
|
||||
}()
|
||||
|
||||
fn, ok := execCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
err = fn(ctx)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
func TestExecuteWithInvalidProcessJson(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
pidFilePath := filepath.Join(tmpdir, "pid")
|
||||
consolePath := "/dev/ptmx"
|
||||
detach := false
|
||||
|
||||
flagSet := testExecParamsSetup(t, pidFilePath, consolePath, detach)
|
||||
|
||||
processPath := filepath.Join(tmpdir, "process.json")
|
||||
flagSet.String("process", processPath, "")
|
||||
|
||||
f, err := os.OpenFile(processPath, os.O_RDWR|os.O_CREATE, testFileMode)
|
||||
assert.NoError(err)
|
||||
|
||||
// invalidate the JSON
|
||||
_, err = f.WriteString("{")
|
||||
assert.NoError(err)
|
||||
f.Close()
|
||||
|
||||
defer os.Remove(processPath)
|
||||
|
||||
flagSet.Parse([]string{testContainerID})
|
||||
ctx := cli.NewContext(cli.NewApp(), flagSet, nil)
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
annotations := map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
}
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, annotations), nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
fn, ok := execCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
err = fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestExecuteWithValidProcessJson(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
pidFilePath := filepath.Join(tmpdir, "pid")
|
||||
consolePath := "/dev/ptmx"
|
||||
|
||||
flagSet := testExecParamsSetup(t, pidFilePath, consolePath, false)
|
||||
|
||||
processPath := filepath.Join(tmpdir, "process.json")
|
||||
flagSet.String("process", processPath, "")
|
||||
|
||||
flagSet.Parse([]string{testContainerID, "/tmp/foo"})
|
||||
ctx := cli.NewContext(cli.NewApp(), flagSet, nil)
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
annotations := map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
}
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, annotations), nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
processJSON := `{
|
||||
"consoleSize": {
|
||||
"height": 15,
|
||||
"width": 15
|
||||
},
|
||||
"terminal": true,
|
||||
"user": {
|
||||
"uid": 0,
|
||||
"gid": 0
|
||||
},
|
||||
"args": [
|
||||
"sh"
|
||||
],
|
||||
"env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"TERM=xterm"
|
||||
],
|
||||
"cwd": "/"
|
||||
}`
|
||||
|
||||
f, err := os.OpenFile(processPath, os.O_RDWR|os.O_CREATE, testFileMode)
|
||||
assert.NoError(err)
|
||||
|
||||
_, err = f.WriteString(processJSON)
|
||||
assert.NoError(err)
|
||||
f.Close()
|
||||
|
||||
defer os.Remove(processPath)
|
||||
|
||||
workload := []string{"cat", "/dev/null"}
|
||||
|
||||
testingImpl.EnterContainerFunc = func(podID, containerID string, cmd vc.Cmd) (vc.VCPod, vc.VCContainer, *vc.Process, error) {
|
||||
// create a fake container process
|
||||
command := exec.Command(workload[0], workload[1:]...)
|
||||
err := command.Start()
|
||||
assert.NoError(err, "Unable to start process %v: %s", workload, err)
|
||||
|
||||
vcProcess := vc.Process{}
|
||||
vcProcess.Pid = command.Process.Pid
|
||||
|
||||
return &vcMock.Pod{}, &vcMock.Container{}, &vcProcess, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.EnterContainerFunc = nil
|
||||
os.Remove(pidFilePath)
|
||||
}()
|
||||
|
||||
fn, ok := execCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
err = fn(ctx)
|
||||
exitErr, ok := err.(*cli.ExitError)
|
||||
assert.True(ok, true, "Exit code not received for fake workload process")
|
||||
assert.Equal(exitErr.ExitCode(), 0, "Exit code should have been 0 for fake workload %s", workload)
|
||||
}
|
||||
|
||||
func TestExecuteWithInvalidEnvironment(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
processPath := filepath.Join(tmpdir, "process.json")
|
||||
|
||||
flagSet := flag.NewFlagSet("", 0)
|
||||
flagSet.String("process", processPath, "")
|
||||
flagSet.Parse([]string{testContainerID})
|
||||
ctx := cli.NewContext(cli.NewApp(), flagSet, nil)
|
||||
|
||||
configPath := testConfigSetup(t)
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
annotations := map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
vcAnnotations.ConfigJSONKey: configJSON,
|
||||
}
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, annotations), nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
processJSON := `{
|
||||
"env": [
|
||||
"TERM="
|
||||
]
|
||||
}`
|
||||
|
||||
f, err := os.OpenFile(processPath, os.O_RDWR|os.O_CREATE, testFileMode)
|
||||
assert.NoError(err)
|
||||
|
||||
_, err = f.WriteString(processJSON)
|
||||
assert.NoError(err)
|
||||
f.Close()
|
||||
|
||||
defer os.Remove(processPath)
|
||||
|
||||
fn, ok := execCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
// vcAnnotations.EnvVars error due to incorrect environment
|
||||
err = fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestGenerateExecParams(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
pidFilePath := filepath.Join(tmpdir, "pid")
|
||||
consolePath := "/dev/ptmx"
|
||||
consoleSocket := "/tmp/console-sock"
|
||||
processLabel := "testlabel"
|
||||
user := "root"
|
||||
cwd := "cwd"
|
||||
apparmor := "apparmorProf"
|
||||
|
||||
flagSet := flag.NewFlagSet("", 0)
|
||||
flagSet.String("pid-file", pidFilePath, "")
|
||||
flagSet.String("console", consolePath, "")
|
||||
flagSet.String("console-socket", consoleSocket, "")
|
||||
flagSet.String("process-label", processLabel, "")
|
||||
|
||||
flagSet.String("user", user, "")
|
||||
flagSet.String("cwd", cwd, "")
|
||||
flagSet.String("apparmor", apparmor, "")
|
||||
|
||||
ctx := cli.NewContext(cli.NewApp(), flagSet, nil)
|
||||
process := &oci.CompatOCIProcess{}
|
||||
params, err := generateExecParams(ctx, process)
|
||||
assert.NoError(err)
|
||||
|
||||
assert.Equal(params.pidFile, pidFilePath)
|
||||
assert.Equal(params.console, consolePath)
|
||||
assert.Equal(params.consoleSock, consoleSocket)
|
||||
assert.Equal(params.processLabel, processLabel)
|
||||
assert.Equal(params.noSubreaper, false)
|
||||
assert.Equal(params.detach, false)
|
||||
|
||||
assert.Equal(params.ociProcess.Terminal, false)
|
||||
assert.Equal(params.ociProcess.User.UID, uint32(0))
|
||||
assert.Equal(params.ociProcess.User.GID, uint32(0))
|
||||
assert.Equal(params.ociProcess.Cwd, cwd)
|
||||
assert.Equal(params.ociProcess.ApparmorProfile, apparmor)
|
||||
}
|
||||
|
||||
func TestGenerateExecParamsWithProcessJsonFile(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
pidFilePath := filepath.Join(tmpdir, "pid")
|
||||
consolePath := "/dev/ptmx"
|
||||
consoleSocket := "/tmp/console-sock"
|
||||
detach := true
|
||||
processLabel := "testlabel"
|
||||
|
||||
flagSet := flag.NewFlagSet("", 0)
|
||||
flagSet.String("pid-file", pidFilePath, "")
|
||||
flagSet.String("console", consolePath, "")
|
||||
flagSet.String("console-socket", consoleSocket, "")
|
||||
flagSet.Bool("detach", detach, "")
|
||||
flagSet.String("process-label", processLabel, "")
|
||||
|
||||
processPath := filepath.Join(tmpdir, "process.json")
|
||||
flagSet.String("process", processPath, "")
|
||||
|
||||
flagSet.Parse([]string{testContainerID})
|
||||
ctx := cli.NewContext(cli.NewApp(), flagSet, nil)
|
||||
|
||||
processJSON := `{
|
||||
"consoleSize": {
|
||||
"height": 15,
|
||||
"width": 15
|
||||
},
|
||||
"terminal": true,
|
||||
"user": {
|
||||
"uid": 0,
|
||||
"gid": 0
|
||||
},
|
||||
"args": [
|
||||
"sh"
|
||||
],
|
||||
"env": [
|
||||
"TERM=xterm",
|
||||
"foo=bar"
|
||||
],
|
||||
"cwd": "/"
|
||||
}`
|
||||
|
||||
f, err := os.OpenFile(processPath, os.O_RDWR|os.O_CREATE, testFileMode)
|
||||
assert.NoError(err)
|
||||
|
||||
_, err = f.WriteString(processJSON)
|
||||
assert.NoError(err)
|
||||
f.Close()
|
||||
|
||||
defer os.Remove(processPath)
|
||||
|
||||
process := &oci.CompatOCIProcess{}
|
||||
params, err := generateExecParams(ctx, process)
|
||||
assert.NoError(err)
|
||||
|
||||
assert.Equal(params.pidFile, pidFilePath)
|
||||
assert.Equal(params.console, consolePath)
|
||||
assert.Equal(params.consoleSock, consoleSocket)
|
||||
assert.Equal(params.processLabel, processLabel)
|
||||
assert.Equal(params.noSubreaper, false)
|
||||
assert.Equal(params.detach, detach)
|
||||
|
||||
assert.Equal(params.ociProcess.Terminal, true)
|
||||
assert.Equal(params.ociProcess.ConsoleSize.Height, uint(15))
|
||||
assert.Equal(params.ociProcess.ConsoleSize.Width, uint(15))
|
||||
assert.Equal(params.ociProcess.User.UID, uint32(0))
|
||||
assert.Equal(params.ociProcess.User.GID, uint32(0))
|
||||
assert.Equal(params.ociProcess.Cwd, "/")
|
||||
assert.Equal(params.ociProcess.Env[0], "TERM=xterm")
|
||||
assert.Equal(params.ociProcess.Env[1], "foo=bar")
|
||||
}
|
37
cli/exit.go
Normal file
37
cli/exit.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import "os"
|
||||
|
||||
var atexitFuncs []func()
|
||||
|
||||
var exitFunc = os.Exit
|
||||
|
||||
// atexit registers a function f that will be run when exit is called. The
|
||||
// handlers so registered will be called the in reverse order of their
|
||||
// registration.
|
||||
func atexit(f func()) {
|
||||
atexitFuncs = append(atexitFuncs, f)
|
||||
}
|
||||
|
||||
// exit calls all atexit handlers before exiting the process with status.
|
||||
func exit(status int) {
|
||||
for i := len(atexitFuncs) - 1; i >= 0; i-- {
|
||||
f := atexitFuncs[i]
|
||||
f()
|
||||
}
|
||||
exitFunc(status)
|
||||
}
|
51
cli/exit_test.go
Normal file
51
cli/exit_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var testFoo string
|
||||
|
||||
func testFunc() {
|
||||
testFoo = "bar"
|
||||
}
|
||||
|
||||
func TestExit(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var testExitStatus int
|
||||
exitFunc = func(status int) {
|
||||
testExitStatus = status
|
||||
}
|
||||
|
||||
defer func() {
|
||||
exitFunc = os.Exit
|
||||
}()
|
||||
|
||||
// test with no atexit functions added.
|
||||
exit(1)
|
||||
assert.Equal(testExitStatus, 1)
|
||||
|
||||
// test with a function added to the atexit list.
|
||||
atexit(testFunc)
|
||||
exit(0)
|
||||
assert.Equal(testFoo, "bar")
|
||||
assert.Equal(testExitStatus, 0)
|
||||
}
|
80
cli/fatal.go
Normal file
80
cli/fatal.go
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2018 Intel Corporation.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/signal"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// List of fatal signals
|
||||
var sigFatal = map[syscall.Signal]bool{
|
||||
syscall.SIGABRT: true,
|
||||
syscall.SIGBUS: true,
|
||||
syscall.SIGILL: true,
|
||||
syscall.SIGQUIT: true,
|
||||
syscall.SIGSEGV: true,
|
||||
syscall.SIGSTKFLT: true,
|
||||
syscall.SIGSYS: true,
|
||||
syscall.SIGTRAP: true,
|
||||
}
|
||||
|
||||
func handlePanic() {
|
||||
r := recover()
|
||||
|
||||
if r != nil {
|
||||
msg := fmt.Sprintf("%s", r)
|
||||
kataLog.WithField("panic", msg).Error("fatal error")
|
||||
|
||||
die()
|
||||
}
|
||||
}
|
||||
|
||||
func backtrace() {
|
||||
profiles := pprof.Profiles()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
for _, p := range profiles {
|
||||
// The magic number requests a full stacktrace. See
|
||||
// https://golang.org/pkg/runtime/pprof/#Profile.WriteTo.
|
||||
pprof.Lookup(p.Name()).WriteTo(buf, 2)
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(buf.String(), "\n") {
|
||||
kataLog.Error(line)
|
||||
}
|
||||
}
|
||||
|
||||
func fatalSignal(sig syscall.Signal) bool {
|
||||
return sigFatal[sig]
|
||||
}
|
||||
|
||||
func fatalSignals() []syscall.Signal {
|
||||
var signals []syscall.Signal
|
||||
|
||||
for sig := range sigFatal {
|
||||
signals = append(signals, sig)
|
||||
|
||||
}
|
||||
|
||||
return signals
|
||||
}
|
||||
|
||||
func die() {
|
||||
backtrace()
|
||||
|
||||
if crashOnError {
|
||||
signal.Reset(syscall.SIGABRT)
|
||||
syscall.Kill(0, syscall.SIGABRT)
|
||||
}
|
||||
|
||||
exit(1)
|
||||
}
|
304
cli/kata-check.go
Normal file
304
cli/kata-check.go
Normal file
@ -0,0 +1,304 @@
|
||||
// Copyright (c) 2017-2018 Intel Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Note: To add a new architecture, implement all identifiers beginning "arch".
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type kernelModule struct {
|
||||
// description
|
||||
desc string
|
||||
|
||||
// maps parameter names to values
|
||||
parameters map[string]string
|
||||
}
|
||||
|
||||
type vmContainerCapableDetails struct {
|
||||
cpuInfoFile string
|
||||
requiredCPUFlags map[string]string
|
||||
requiredCPUAttribs map[string]string
|
||||
requiredKernelModules map[string]kernelModule
|
||||
}
|
||||
|
||||
const (
|
||||
moduleParamDir = "parameters"
|
||||
cpuFlagsTag = "flags"
|
||||
successMessageCapable = "System is capable of running " + project
|
||||
successMessageCreate = "System can currently create " + project
|
||||
failMessage = "System is not capable of running " + project
|
||||
kernelPropertyCorrect = "Kernel property value correct"
|
||||
)
|
||||
|
||||
// variables rather than consts to allow tests to modify them
|
||||
var (
|
||||
procCPUInfo = "/proc/cpuinfo"
|
||||
sysModuleDir = "/sys/module"
|
||||
modInfoCmd = "modinfo"
|
||||
)
|
||||
|
||||
// getCPUInfo returns details of the first CPU read from the specified cpuinfo file
|
||||
func getCPUInfo(cpuInfoFile string) (string, error) {
|
||||
text, err := getFileContents(cpuInfoFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cpus := strings.SplitAfter(text, "\n\n")
|
||||
|
||||
trimmed := strings.TrimSpace(cpus[0])
|
||||
if trimmed == "" {
|
||||
return "", fmt.Errorf("Cannot determine CPU details")
|
||||
}
|
||||
|
||||
return trimmed, nil
|
||||
}
|
||||
|
||||
// findAnchoredString searches haystack for needle and returns true if found
|
||||
func findAnchoredString(haystack, needle string) bool {
|
||||
if haystack == "" || needle == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ensure the search string is anchored
|
||||
pattern := regexp.MustCompile(`\b` + needle + `\b`)
|
||||
|
||||
return pattern.MatchString(haystack)
|
||||
}
|
||||
|
||||
// getCPUFlags returns the CPU flags from the cpuinfo file specified
|
||||
func getCPUFlags(cpuinfo string) string {
|
||||
for _, line := range strings.Split(cpuinfo, "\n") {
|
||||
if strings.HasPrefix(line, cpuFlagsTag) {
|
||||
fields := strings.Split(line, ":")
|
||||
if len(fields) == 2 {
|
||||
return strings.TrimSpace(fields[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// haveKernelModule returns true if the specified module exists
|
||||
// (either loaded or available to be loaded)
|
||||
func haveKernelModule(module string) bool {
|
||||
// First, check to see if the module is already loaded
|
||||
path := filepath.Join(sysModuleDir, module)
|
||||
if fileExists(path) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Now, check if the module is unloaded, but available
|
||||
cmd := exec.Command(modInfoCmd, module)
|
||||
err := cmd.Run()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// checkCPU checks all required CPU attributes modules and returns a count of
|
||||
// the number of CPU attribute errors (all of which are logged by this
|
||||
// function). The specified tag is simply used for logging purposes.
|
||||
func checkCPU(tag, cpuinfo string, attribs map[string]string) (count uint32) {
|
||||
if cpuinfo == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
for attrib, desc := range attribs {
|
||||
fields := logrus.Fields{
|
||||
"type": tag,
|
||||
"name": attrib,
|
||||
"description": desc,
|
||||
}
|
||||
|
||||
found := findAnchoredString(cpuinfo, attrib)
|
||||
if !found {
|
||||
kataLog.WithFields(fields).Errorf("CPU property not found")
|
||||
count++
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
kataLog.WithFields(fields).Infof("CPU property found")
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func checkCPUFlags(cpuflags string, required map[string]string) uint32 {
|
||||
return checkCPU("flag", cpuflags, required)
|
||||
}
|
||||
|
||||
func checkCPUAttribs(cpuinfo string, attribs map[string]string) uint32 {
|
||||
return checkCPU("attribute", cpuinfo, attribs)
|
||||
}
|
||||
|
||||
// kernelParamHandler represents a function that allows kernel module
|
||||
// parameter errors to be ignored for special scenarios.
|
||||
//
|
||||
// The function is passed the following parameters:
|
||||
//
|
||||
// onVMM - `true` if the host is running under a VMM environment
|
||||
// fields - A set of fields showing the expected and actual module parameter values.
|
||||
// msg - The message that would be logged showing the incorrect kernel module
|
||||
// parameter.
|
||||
//
|
||||
// The function must return `true` if the kernel module parameter error should
|
||||
// be ignored, or `false` if it is a real error.
|
||||
//
|
||||
// Note: it is up to the function to add an appropriate log call if the error
|
||||
// should be ignored.
|
||||
type kernelParamHandler func(onVMM bool, fields logrus.Fields, msg string) bool
|
||||
|
||||
// checkKernelModules checks all required kernel modules modules and returns a count of
|
||||
// the number of module errors (all of which are logged by this
|
||||
// function). Only fatal errors result in an error return.
|
||||
func checkKernelModules(modules map[string]kernelModule, handler kernelParamHandler) (count uint32, err error) {
|
||||
onVMM, err := vc.RunningOnVMM(procCPUInfo)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for module, details := range modules {
|
||||
fields := logrus.Fields{
|
||||
"type": "module",
|
||||
"name": module,
|
||||
"description": details.desc,
|
||||
}
|
||||
|
||||
if !haveKernelModule(module) {
|
||||
kataLog.WithFields(fields).Error("kernel property not found")
|
||||
count++
|
||||
continue
|
||||
}
|
||||
|
||||
kataLog.WithFields(fields).Infof("kernel property found")
|
||||
|
||||
for param, expected := range details.parameters {
|
||||
path := filepath.Join(sysModuleDir, module, moduleParamDir, param)
|
||||
value, err := getFileContents(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
value = strings.TrimRight(value, "\n\r")
|
||||
|
||||
fields["parameter"] = param
|
||||
fields["value"] = value
|
||||
|
||||
if value != expected {
|
||||
fields["expected"] = expected
|
||||
|
||||
msg := "kernel module parameter has unexpected value"
|
||||
|
||||
if handler != nil {
|
||||
ignoreError := handler(onVMM, fields, msg)
|
||||
if ignoreError {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
kataLog.WithFields(fields).Error(msg)
|
||||
count++
|
||||
}
|
||||
|
||||
kataLog.WithFields(fields).Info(kernelPropertyCorrect)
|
||||
}
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// hostIsVMContainerCapable checks to see if the host is theoretically capable
|
||||
// of creating a VM container.
|
||||
func hostIsVMContainerCapable(details vmContainerCapableDetails) error {
|
||||
cpuinfo, err := getCPUInfo(details.cpuInfoFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cpuFlags := getCPUFlags(cpuinfo)
|
||||
if cpuFlags == "" {
|
||||
return fmt.Errorf("Cannot find CPU flags")
|
||||
}
|
||||
|
||||
// Keep a track of the error count, but don't error until all tests
|
||||
// have been performed!
|
||||
errorCount := uint32(0)
|
||||
|
||||
count := checkCPUAttribs(cpuinfo, details.requiredCPUAttribs)
|
||||
|
||||
errorCount += count
|
||||
|
||||
count = checkCPUFlags(cpuFlags, details.requiredCPUFlags)
|
||||
|
||||
errorCount += count
|
||||
|
||||
count, err = checkKernelModules(details.requiredKernelModules, archKernelParamHandler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errorCount += count
|
||||
|
||||
if errorCount == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("ERROR: %s", failMessage)
|
||||
}
|
||||
|
||||
var kataCheckCLICommand = cli.Command{
|
||||
Name: checkCmd,
|
||||
Usage: "tests if system can run " + project,
|
||||
Action: func(context *cli.Context) error {
|
||||
|
||||
details := vmContainerCapableDetails{
|
||||
cpuInfoFile: procCPUInfo,
|
||||
requiredCPUFlags: archRequiredCPUFlags,
|
||||
requiredCPUAttribs: archRequiredCPUAttribs,
|
||||
requiredKernelModules: archRequiredKernelModules,
|
||||
}
|
||||
|
||||
err := hostIsVMContainerCapable(details)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kataLog.Info(successMessageCapable)
|
||||
|
||||
if os.Geteuid() == 0 {
|
||||
err = archHostCanCreateVMContainer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kataLog.Info(successMessageCreate)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
132
cli/kata-check_amd64.go
Normal file
132
cli/kata-check_amd64.go
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2018 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
/*
|
||||
#include <linux/kvm.h>
|
||||
|
||||
const int ioctl_KVM_CREATE_VM = KVM_CREATE_VM;
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// variables rather than consts to allow tests to modify them
|
||||
var (
|
||||
kvmDevice = "/dev/kvm"
|
||||
)
|
||||
|
||||
// archRequiredCPUFlags maps a CPU flag value to search for and a
|
||||
// human-readable description of that value.
|
||||
var archRequiredCPUFlags = map[string]string{
|
||||
"vmx": "Virtualization support",
|
||||
"lm": "64Bit CPU",
|
||||
"sse4_1": "SSE4.1",
|
||||
}
|
||||
|
||||
// archRequiredCPUAttribs maps a CPU (non-CPU flag) attribute value to search for
|
||||
// and a human-readable description of that value.
|
||||
var archRequiredCPUAttribs = map[string]string{
|
||||
"GenuineIntel": "Intel Architecture CPU",
|
||||
}
|
||||
|
||||
// archRequiredKernelModules maps a required module name to a human-readable
|
||||
// description of the modules functionality and an optional list of
|
||||
// required module parameters.
|
||||
var archRequiredKernelModules = map[string]kernelModule{
|
||||
"kvm": {
|
||||
desc: "Kernel-based Virtual Machine",
|
||||
},
|
||||
"kvm_intel": {
|
||||
desc: "Intel KVM",
|
||||
parameters: map[string]string{
|
||||
"nested": "Y",
|
||||
// "VMX Unrestricted mode support". This is used
|
||||
// as a heuristic to determine if the system is
|
||||
// "new enough" to run a Kata Container
|
||||
// (atleast a Westmere).
|
||||
"unrestricted_guest": "Y",
|
||||
},
|
||||
},
|
||||
"vhost": {
|
||||
desc: "Host kernel accelerator for virtio",
|
||||
},
|
||||
"vhost_net": {
|
||||
desc: "Host kernel accelerator for virtio network",
|
||||
},
|
||||
}
|
||||
|
||||
// kvmIsUsable determines if it will be possible to create a full virtual machine
|
||||
// by creating a minimal VM and then deleting it.
|
||||
func kvmIsUsable() error {
|
||||
flags := syscall.O_RDWR | syscall.O_CLOEXEC
|
||||
|
||||
f, err := syscall.Open(kvmDevice, flags, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(f)
|
||||
|
||||
fieldLogger := kataLog.WithField("check-type", "full")
|
||||
|
||||
fieldLogger.WithField("device", kvmDevice).Info("device available")
|
||||
|
||||
vm, _, errno := syscall.Syscall(syscall.SYS_IOCTL,
|
||||
uintptr(f),
|
||||
uintptr(C.ioctl_KVM_CREATE_VM),
|
||||
0)
|
||||
if errno != 0 {
|
||||
if errno == syscall.EBUSY {
|
||||
fieldLogger.WithField("reason", "another hypervisor running").Error("cannot create VM")
|
||||
}
|
||||
|
||||
return errno
|
||||
}
|
||||
defer syscall.Close(int(vm))
|
||||
|
||||
fieldLogger.WithField("feature", "create-vm").Info("feature available")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func archHostCanCreateVMContainer() error {
|
||||
return kvmIsUsable()
|
||||
}
|
||||
|
||||
func archKernelParamHandler(onVMM bool, fields logrus.Fields, msg string) bool {
|
||||
param, ok := fields["parameter"].(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// This option is not required when
|
||||
// already running under a hypervisor.
|
||||
if param == "unrestricted_guest" && onVMM {
|
||||
kataLog.WithFields(fields).Warn(kernelPropertyCorrect)
|
||||
return true
|
||||
}
|
||||
|
||||
if param == "nested" {
|
||||
kataLog.WithFields(fields).Warn(msg)
|
||||
return true
|
||||
}
|
||||
|
||||
// don't ignore the error
|
||||
return false
|
||||
}
|
466
cli/kata-check_amd64_test.go
Normal file
466
cli/kata-check_amd64_test.go
Normal file
@ -0,0 +1,466 @@
|
||||
// Copyright (c) 2018 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func setupCheckHostIsVMContainerCapable(assert *assert.Assertions, cpuInfoFile string, cpuData []testCPUData, moduleData []testModuleData) {
|
||||
createModules(assert, cpuInfoFile, moduleData)
|
||||
|
||||
// all the modules files have now been created, so deal with the
|
||||
// cpuinfo data.
|
||||
for _, d := range cpuData {
|
||||
err := makeCPUInfoFile(cpuInfoFile, d.vendorID, d.flags)
|
||||
assert.NoError(err)
|
||||
|
||||
details := vmContainerCapableDetails{
|
||||
cpuInfoFile: cpuInfoFile,
|
||||
requiredCPUFlags: archRequiredCPUFlags,
|
||||
requiredCPUAttribs: archRequiredCPUAttribs,
|
||||
requiredKernelModules: archRequiredKernelModules,
|
||||
}
|
||||
|
||||
err = hostIsVMContainerCapable(details)
|
||||
if d.expectError {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCCCheckCLIFunction(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
savedSysModuleDir := sysModuleDir
|
||||
savedProcCPUInfo := procCPUInfo
|
||||
|
||||
cpuInfoFile := filepath.Join(dir, "cpuinfo")
|
||||
|
||||
// XXX: override
|
||||
sysModuleDir = filepath.Join(dir, "sys/module")
|
||||
procCPUInfo = cpuInfoFile
|
||||
|
||||
defer func() {
|
||||
sysModuleDir = savedSysModuleDir
|
||||
procCPUInfo = savedProcCPUInfo
|
||||
}()
|
||||
|
||||
err = os.MkdirAll(sysModuleDir, testDirMode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cpuData := []testCPUData{
|
||||
{"GenuineIntel", "lm vmx sse4_1", false},
|
||||
}
|
||||
|
||||
moduleData := []testModuleData{
|
||||
{filepath.Join(sysModuleDir, "kvm_intel/parameters/unrestricted_guest"), false, "Y"},
|
||||
{filepath.Join(sysModuleDir, "kvm_intel/parameters/nested"), false, "Y"},
|
||||
}
|
||||
|
||||
devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0666)
|
||||
assert.NoError(err)
|
||||
defer devNull.Close()
|
||||
|
||||
savedLogOutput := kataLog.Logger.Out
|
||||
|
||||
// discard normal output
|
||||
kataLog.Logger.Out = devNull
|
||||
|
||||
defer func() {
|
||||
kataLog.Logger.Out = savedLogOutput
|
||||
}()
|
||||
|
||||
setupCheckHostIsVMContainerCapable(assert, cpuInfoFile, cpuData, moduleData)
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, nil, nil)
|
||||
app.Name = "foo"
|
||||
|
||||
// create buffer to save logger output
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
// capture output this time
|
||||
kataLog.Logger.Out = buf
|
||||
|
||||
fn, ok := kataCheckCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
err = fn(ctx)
|
||||
assert.NoError(err)
|
||||
|
||||
output := buf.String()
|
||||
|
||||
for _, c := range cpuData {
|
||||
assert.True(findAnchoredString(output, c.vendorID))
|
||||
for _, flag := range strings.Fields(c.flags) {
|
||||
assert.True(findAnchoredString(output, flag))
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range moduleData {
|
||||
name := path.Base(m.path)
|
||||
assert.True(findAnchoredString(output, name))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCheckKernelModulesNoNesting(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
savedSysModuleDir := sysModuleDir
|
||||
savedProcCPUInfo := procCPUInfo
|
||||
|
||||
cpuInfoFile := filepath.Join(dir, "cpuinfo")
|
||||
|
||||
// XXX: override
|
||||
sysModuleDir = filepath.Join(dir, "sys/module")
|
||||
procCPUInfo = cpuInfoFile
|
||||
|
||||
defer func() {
|
||||
sysModuleDir = savedSysModuleDir
|
||||
procCPUInfo = savedProcCPUInfo
|
||||
}()
|
||||
|
||||
err = os.MkdirAll(sysModuleDir, testDirMode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
requiredModules := map[string]kernelModule{
|
||||
"kvm_intel": {
|
||||
desc: "Intel KVM",
|
||||
parameters: map[string]string{
|
||||
"nested": "Y",
|
||||
"unrestricted_guest": "Y",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actualModuleData := []testModuleData{
|
||||
{filepath.Join(sysModuleDir, "kvm"), true, ""},
|
||||
{filepath.Join(sysModuleDir, "kvm_intel"), true, ""},
|
||||
{filepath.Join(sysModuleDir, "kvm_intel/parameters/unrestricted_guest"), false, "Y"},
|
||||
|
||||
// XXX: force a warning
|
||||
{filepath.Join(sysModuleDir, "kvm_intel/parameters/nested"), false, "N"},
|
||||
}
|
||||
|
||||
vendor := "GenuineIntel"
|
||||
flags := "vmx lm sse4_1 hypervisor"
|
||||
|
||||
_, err = checkKernelModules(requiredModules, archKernelParamHandler)
|
||||
// no cpuInfoFile yet
|
||||
assert.Error(err)
|
||||
|
||||
createModules(assert, cpuInfoFile, actualModuleData)
|
||||
|
||||
err = makeCPUInfoFile(cpuInfoFile, vendor, flags)
|
||||
assert.NoError(err)
|
||||
|
||||
count, err := checkKernelModules(requiredModules, archKernelParamHandler)
|
||||
assert.NoError(err)
|
||||
assert.Equal(count, uint32(0))
|
||||
|
||||
// create buffer to save logger output
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
savedLogOutput := kataLog.Logger.Out
|
||||
|
||||
defer func() {
|
||||
kataLog.Logger.Out = savedLogOutput
|
||||
}()
|
||||
|
||||
kataLog.Logger.Out = buf
|
||||
|
||||
count, err = checkKernelModules(requiredModules, archKernelParamHandler)
|
||||
|
||||
assert.NoError(err)
|
||||
assert.Equal(count, uint32(0))
|
||||
|
||||
re := regexp.MustCompile(`\bwarning\b.*\bnested\b`)
|
||||
matches := re.FindAllStringSubmatch(buf.String(), -1)
|
||||
assert.NotEmpty(matches)
|
||||
}
|
||||
|
||||
func TestCheckCheckKernelModulesNoUnrestrictedGuest(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
savedSysModuleDir := sysModuleDir
|
||||
savedProcCPUInfo := procCPUInfo
|
||||
|
||||
cpuInfoFile := filepath.Join(dir, "cpuinfo")
|
||||
|
||||
// XXX: override
|
||||
sysModuleDir = filepath.Join(dir, "sys/module")
|
||||
procCPUInfo = cpuInfoFile
|
||||
|
||||
defer func() {
|
||||
sysModuleDir = savedSysModuleDir
|
||||
procCPUInfo = savedProcCPUInfo
|
||||
}()
|
||||
|
||||
err = os.MkdirAll(sysModuleDir, testDirMode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
requiredModules := map[string]kernelModule{
|
||||
"kvm_intel": {
|
||||
desc: "Intel KVM",
|
||||
parameters: map[string]string{
|
||||
"nested": "Y",
|
||||
"unrestricted_guest": "Y",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actualModuleData := []testModuleData{
|
||||
{filepath.Join(sysModuleDir, "kvm"), true, ""},
|
||||
{filepath.Join(sysModuleDir, "kvm_intel"), true, ""},
|
||||
{filepath.Join(sysModuleDir, "kvm_intel/parameters/nested"), false, "Y"},
|
||||
|
||||
// XXX: force a failure on non-VMM systems
|
||||
{filepath.Join(sysModuleDir, "kvm_intel/parameters/unrestricted_guest"), false, "N"},
|
||||
}
|
||||
|
||||
vendor := "GenuineIntel"
|
||||
flags := "vmx lm sse4_1"
|
||||
|
||||
_, err = checkKernelModules(requiredModules, archKernelParamHandler)
|
||||
// no cpuInfoFile yet
|
||||
assert.Error(err)
|
||||
|
||||
err = makeCPUInfoFile(cpuInfoFile, vendor, flags)
|
||||
assert.NoError(err)
|
||||
|
||||
createModules(assert, cpuInfoFile, actualModuleData)
|
||||
|
||||
count, err := checkKernelModules(requiredModules, archKernelParamHandler)
|
||||
|
||||
assert.NoError(err)
|
||||
// fails due to unrestricted_guest not being available
|
||||
assert.Equal(count, uint32(1))
|
||||
|
||||
// pretend test is running under a hypervisor
|
||||
flags += " hypervisor"
|
||||
|
||||
// recreate
|
||||
err = makeCPUInfoFile(cpuInfoFile, vendor, flags)
|
||||
assert.NoError(err)
|
||||
|
||||
// create buffer to save logger output
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
savedLogOutput := kataLog.Logger.Out
|
||||
|
||||
defer func() {
|
||||
kataLog.Logger.Out = savedLogOutput
|
||||
}()
|
||||
|
||||
kataLog.Logger.Out = buf
|
||||
|
||||
count, err = checkKernelModules(requiredModules, archKernelParamHandler)
|
||||
|
||||
// no error now because running under a hypervisor
|
||||
assert.NoError(err)
|
||||
assert.Equal(count, uint32(0))
|
||||
|
||||
re := regexp.MustCompile(`\bwarning\b.*\bunrestricted_guest\b`)
|
||||
matches := re.FindAllStringSubmatch(buf.String(), -1)
|
||||
assert.NotEmpty(matches)
|
||||
}
|
||||
|
||||
func TestCheckHostIsVMContainerCapable(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
savedSysModuleDir := sysModuleDir
|
||||
savedProcCPUInfo := procCPUInfo
|
||||
|
||||
cpuInfoFile := filepath.Join(dir, "cpuinfo")
|
||||
|
||||
// XXX: override
|
||||
sysModuleDir = filepath.Join(dir, "sys/module")
|
||||
procCPUInfo = cpuInfoFile
|
||||
|
||||
defer func() {
|
||||
sysModuleDir = savedSysModuleDir
|
||||
procCPUInfo = savedProcCPUInfo
|
||||
}()
|
||||
|
||||
err = os.MkdirAll(sysModuleDir, testDirMode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cpuData := []testCPUData{
|
||||
{"", "", true},
|
||||
{"Intel", "", true},
|
||||
{"GenuineIntel", "", true},
|
||||
{"GenuineIntel", "lm", true},
|
||||
{"GenuineIntel", "lm vmx", true},
|
||||
{"GenuineIntel", "lm vmx sse4_1", false},
|
||||
}
|
||||
|
||||
moduleData := []testModuleData{
|
||||
{filepath.Join(sysModuleDir, "kvm"), true, ""},
|
||||
{filepath.Join(sysModuleDir, "kvm_intel"), true, ""},
|
||||
{filepath.Join(sysModuleDir, "kvm_intel/parameters/nested"), false, "Y"},
|
||||
{filepath.Join(sysModuleDir, "kvm_intel/parameters/unrestricted_guest"), false, "Y"},
|
||||
}
|
||||
|
||||
setupCheckHostIsVMContainerCapable(assert, cpuInfoFile, cpuData, moduleData)
|
||||
|
||||
// remove the modules to force a failure
|
||||
err = os.RemoveAll(sysModuleDir)
|
||||
assert.NoError(err)
|
||||
|
||||
details := vmContainerCapableDetails{
|
||||
cpuInfoFile: cpuInfoFile,
|
||||
requiredCPUFlags: archRequiredCPUFlags,
|
||||
requiredCPUAttribs: archRequiredCPUAttribs,
|
||||
requiredKernelModules: archRequiredKernelModules,
|
||||
}
|
||||
|
||||
err = hostIsVMContainerCapable(details)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestArchKernelParamHandler(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
type testData struct {
|
||||
onVMM bool
|
||||
fields logrus.Fields
|
||||
msg string
|
||||
expectIgnore bool
|
||||
}
|
||||
|
||||
data := []testData{
|
||||
{true, logrus.Fields{}, "", false},
|
||||
{false, logrus.Fields{}, "", false},
|
||||
|
||||
{
|
||||
false,
|
||||
logrus.Fields{
|
||||
// wrong type
|
||||
"parameter": 123,
|
||||
},
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
false,
|
||||
logrus.Fields{
|
||||
"parameter": "unrestricted_guest",
|
||||
},
|
||||
"",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
true,
|
||||
logrus.Fields{
|
||||
"parameter": "unrestricted_guest",
|
||||
},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
false,
|
||||
logrus.Fields{
|
||||
"parameter": "nested",
|
||||
},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, d := range data {
|
||||
result := archKernelParamHandler(d.onVMM, d.fields, d.msg)
|
||||
if d.expectIgnore {
|
||||
assert.True(result, "test %d (%+v)", i, d)
|
||||
} else {
|
||||
assert.False(result, "test %d (%+v)", i, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKvmIsUsable(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
savedKvmDevice := kvmDevice
|
||||
fakeKVMDevice := filepath.Join(dir, "kvm")
|
||||
kvmDevice = fakeKVMDevice
|
||||
|
||||
defer func() {
|
||||
kvmDevice = savedKvmDevice
|
||||
}()
|
||||
|
||||
err = kvmIsUsable()
|
||||
assert.Error(err)
|
||||
|
||||
err = createEmptyFile(fakeKVMDevice)
|
||||
assert.NoError(err)
|
||||
|
||||
err = kvmIsUsable()
|
||||
assert.Error(err)
|
||||
}
|
58
cli/kata-check_data_amd64_test.go
Normal file
58
cli/kata-check_data_amd64_test.go
Normal file
@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
const testCPUInfoTemplate = `
|
||||
processor : 0
|
||||
vendor_id : {{.VendorID}}
|
||||
cpu family : 6
|
||||
model : 61
|
||||
model name : Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz
|
||||
stepping : 4
|
||||
microcode : 0x25
|
||||
cpu MHz : 1999.987
|
||||
cache size : 4096 KB
|
||||
physical id : 0
|
||||
siblings : 4
|
||||
core id : 0
|
||||
cpu cores : 2
|
||||
apicid : 0
|
||||
initial apicid : 0
|
||||
fpu : yes
|
||||
fpu_exception : yes
|
||||
cpuid level : 20
|
||||
wp : yes
|
||||
flags : {{.Flags}}
|
||||
bugs :
|
||||
bogomips : 5188.36
|
||||
clflush size : 64
|
||||
cache_alignment : 64
|
||||
address sizes : 39 bits physical, 48 bits virtual
|
||||
power management:
|
||||
|
||||
processor : 1
|
||||
vendor_id : {{.VendorID}}
|
||||
cpu family : 6
|
||||
model : 61
|
||||
model name : Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz
|
||||
stepping : 4
|
||||
microcode : 0x25
|
||||
cpu MHz : 1999.987
|
||||
cache size : 4096 KB
|
||||
physical id : 0
|
||||
siblings : 4
|
||||
core id : 0
|
||||
cpu cores : 2
|
||||
apicid : 1
|
||||
initial apicid : 1
|
||||
fpu : yes
|
||||
fpu_exception : yes
|
||||
cpuid level : 20
|
||||
wp : yes
|
||||
flags : {{.Flags}}
|
||||
bugs :
|
||||
bogomips : 5194.90
|
||||
clflush size : 64
|
||||
cache_alignment : 64
|
||||
address sizes : 39 bits physical, 48 bits virtual
|
||||
power management:
|
||||
|
||||
`
|
698
cli/kata-check_test.go
Normal file
698
cli/kata-check_test.go
Normal file
@ -0,0 +1,698 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type testModuleData struct {
|
||||
path string
|
||||
isDir bool
|
||||
contents string
|
||||
}
|
||||
|
||||
type testCPUData struct {
|
||||
vendorID string
|
||||
flags string
|
||||
expectError bool
|
||||
}
|
||||
|
||||
func createFile(file, contents string) error {
|
||||
return ioutil.WriteFile(file, []byte(contents), testFileMode)
|
||||
}
|
||||
|
||||
func createModules(assert *assert.Assertions, cpuInfoFile string, moduleData []testModuleData) {
|
||||
for _, d := range moduleData {
|
||||
var dir string
|
||||
|
||||
if d.isDir {
|
||||
dir = d.path
|
||||
} else {
|
||||
dir = path.Dir(d.path)
|
||||
}
|
||||
|
||||
err := os.MkdirAll(dir, testDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
if !d.isDir {
|
||||
err = createFile(d.path, d.contents)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
details := vmContainerCapableDetails{
|
||||
cpuInfoFile: cpuInfoFile,
|
||||
}
|
||||
|
||||
err = hostIsVMContainerCapable(details)
|
||||
if fileExists(cpuInfoFile) {
|
||||
assert.NoError(err)
|
||||
} else {
|
||||
assert.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkKernelParamHandler(assert *assert.Assertions, kernelModulesToCreate, expectedKernelModules map[string]kernelModule, handler kernelParamHandler, expectHandlerError bool, expectedErrorCount uint32) {
|
||||
err := os.RemoveAll(sysModuleDir)
|
||||
assert.NoError(err)
|
||||
|
||||
count, err := checkKernelModules(map[string]kernelModule{}, handler)
|
||||
|
||||
// No required modules means no error
|
||||
assert.NoError(err)
|
||||
assert.Equal(count, uint32(0))
|
||||
|
||||
count, err = checkKernelModules(expectedKernelModules, handler)
|
||||
assert.NoError(err)
|
||||
|
||||
// No modules exist
|
||||
expectedCount := len(expectedKernelModules)
|
||||
assert.Equal(count, uint32(expectedCount))
|
||||
|
||||
err = os.MkdirAll(sysModuleDir, testDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
for module, details := range kernelModulesToCreate {
|
||||
path := filepath.Join(sysModuleDir, module)
|
||||
err = os.MkdirAll(path, testDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
paramDir := filepath.Join(path, "parameters")
|
||||
err = os.MkdirAll(paramDir, testDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
for param, value := range details.parameters {
|
||||
paramPath := filepath.Join(paramDir, param)
|
||||
err = createFile(paramPath, value)
|
||||
assert.NoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
count, err = checkKernelModules(expectedKernelModules, handler)
|
||||
|
||||
if expectHandlerError {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(err)
|
||||
assert.Equal(count, expectedErrorCount)
|
||||
}
|
||||
|
||||
func makeCPUInfoFile(path, vendorID, flags string) error {
|
||||
t := template.New("cpuinfo")
|
||||
|
||||
t, err := t.Parse(testCPUInfoTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := map[string]string{
|
||||
"Flags": flags,
|
||||
"VendorID": vendorID,
|
||||
}
|
||||
|
||||
contents := &bytes.Buffer{}
|
||||
|
||||
err = t.Execute(contents, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(path, contents.Bytes(), testFileMode)
|
||||
}
|
||||
|
||||
func TestCheckGetCPUInfo(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
type testData struct {
|
||||
contents string
|
||||
expectedResult string
|
||||
expectError bool
|
||||
}
|
||||
|
||||
data := []testData{
|
||||
{"", "", true},
|
||||
{" ", "", true},
|
||||
{"\n", "", true},
|
||||
{"\n\n", "", true},
|
||||
{"hello\n", "hello", false},
|
||||
{"foo\n\n", "foo", false},
|
||||
{"foo\n\nbar\n\n", "foo", false},
|
||||
{"foo\n\nbar\nbaz\n\n", "foo", false},
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
file := filepath.Join(dir, "cpuinfo")
|
||||
// file doesn't exist
|
||||
_, err = getCPUInfo(file)
|
||||
assert.Error(err)
|
||||
|
||||
for _, d := range data {
|
||||
err = ioutil.WriteFile(file, []byte(d.contents), testFileMode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(file)
|
||||
|
||||
contents, err := getCPUInfo(file)
|
||||
if d.expectError {
|
||||
assert.Error(err, fmt.Sprintf("got %q, test data: %+v", contents, d))
|
||||
} else {
|
||||
assert.NoError(err, fmt.Sprintf("got %q, test data: %+v", contents, d))
|
||||
}
|
||||
|
||||
assert.Equal(d.expectedResult, contents)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFindAnchoredString(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
type testData struct {
|
||||
haystack string
|
||||
needle string
|
||||
expectSuccess bool
|
||||
}
|
||||
|
||||
data := []testData{
|
||||
{"", "", false},
|
||||
{"", "foo", false},
|
||||
{"foo", "", false},
|
||||
{"food", "foo", false},
|
||||
{"foo", "foo", true},
|
||||
{"foo bar", "foo", true},
|
||||
{"foo bar baz", "bar", true},
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
result := findAnchoredString(d.haystack, d.needle)
|
||||
|
||||
if d.expectSuccess {
|
||||
assert.True(result)
|
||||
} else {
|
||||
assert.False(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckGetCPUFlags(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
type testData struct {
|
||||
cpuinfo string
|
||||
expectedFlags string
|
||||
}
|
||||
|
||||
data := []testData{
|
||||
{"", ""},
|
||||
{"foo", ""},
|
||||
{"foo bar", ""},
|
||||
{":", ""},
|
||||
{"flags", ""},
|
||||
{"flags:", ""},
|
||||
{"flags: a b c", "a b c"},
|
||||
{"flags: a b c foo bar d", "a b c foo bar d"},
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
result := getCPUFlags(d.cpuinfo)
|
||||
assert.Equal(d.expectedFlags, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCheckCPUFlags(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
type testData struct {
|
||||
cpuflags string
|
||||
required map[string]string
|
||||
expectCount uint32
|
||||
}
|
||||
|
||||
data := []testData{
|
||||
{
|
||||
"",
|
||||
map[string]string{},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"",
|
||||
map[string]string{
|
||||
"a": "A flag",
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"",
|
||||
map[string]string{
|
||||
"a": "A flag",
|
||||
"b": "B flag",
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"a b c",
|
||||
map[string]string{
|
||||
"b": "B flag",
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"a b c",
|
||||
map[string]string{
|
||||
"x": "X flag",
|
||||
"y": "Y flag",
|
||||
"z": "Z flag",
|
||||
},
|
||||
3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
count := checkCPUFlags(d.cpuflags, d.required)
|
||||
assert.Equal(d.expectCount, count, "%+v", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCheckCPUAttribs(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
type testData struct {
|
||||
cpuinfo string
|
||||
required map[string]string
|
||||
expectCount uint32
|
||||
}
|
||||
|
||||
data := []testData{
|
||||
{
|
||||
"",
|
||||
map[string]string{},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"",
|
||||
map[string]string{
|
||||
"a": "",
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"a: b",
|
||||
map[string]string{
|
||||
"b": "B attribute",
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"a: b\nc: d\ne: f",
|
||||
map[string]string{
|
||||
"b": "B attribute",
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"a: b\n",
|
||||
map[string]string{
|
||||
"b": "B attribute",
|
||||
"c": "C attribute",
|
||||
"d": "D attribute",
|
||||
},
|
||||
2,
|
||||
},
|
||||
{
|
||||
"a: b\nc: d\ne: f",
|
||||
map[string]string{
|
||||
"b": "B attribute",
|
||||
"d": "D attribute",
|
||||
"f": "F attribute",
|
||||
},
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
count := checkCPUAttribs(d.cpuinfo, d.required)
|
||||
assert.Equal(d.expectCount, count, "%+v", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckHaveKernelModule(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
savedModInfoCmd := modInfoCmd
|
||||
savedSysModuleDir := sysModuleDir
|
||||
|
||||
// XXX: override (fake the modprobe command failing)
|
||||
modInfoCmd = "false"
|
||||
sysModuleDir = filepath.Join(dir, "sys/module")
|
||||
|
||||
defer func() {
|
||||
modInfoCmd = savedModInfoCmd
|
||||
sysModuleDir = savedSysModuleDir
|
||||
}()
|
||||
|
||||
err = os.MkdirAll(sysModuleDir, testDirMode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
module := "foo"
|
||||
|
||||
result := haveKernelModule(module)
|
||||
assert.False(result)
|
||||
|
||||
// XXX: override - make our fake "modprobe" succeed
|
||||
modInfoCmd = "true"
|
||||
|
||||
result = haveKernelModule(module)
|
||||
assert.True(result)
|
||||
|
||||
// disable "modprobe" again
|
||||
modInfoCmd = "false"
|
||||
|
||||
fooDir := filepath.Join(sysModuleDir, module)
|
||||
err = os.MkdirAll(fooDir, testDirMode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result = haveKernelModule(module)
|
||||
assert.True(result)
|
||||
}
|
||||
|
||||
func TestCheckCheckKernelModules(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
savedModInfoCmd := modInfoCmd
|
||||
savedSysModuleDir := sysModuleDir
|
||||
|
||||
// XXX: override (fake the modprobe command failing)
|
||||
modInfoCmd = "false"
|
||||
sysModuleDir = filepath.Join(dir, "sys/module")
|
||||
|
||||
defer func() {
|
||||
modInfoCmd = savedModInfoCmd
|
||||
sysModuleDir = savedSysModuleDir
|
||||
}()
|
||||
|
||||
err = os.MkdirAll(sysModuleDir, testDirMode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testData := map[string]kernelModule{
|
||||
"foo": {
|
||||
desc: "desc",
|
||||
parameters: map[string]string{},
|
||||
},
|
||||
"bar": {
|
||||
desc: "desc",
|
||||
parameters: map[string]string{
|
||||
"param1": "hello",
|
||||
"param2": "world",
|
||||
"param3": "a",
|
||||
"param4": ".",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
count, err := checkKernelModules(map[string]kernelModule{}, nil)
|
||||
// No required modules means no error
|
||||
assert.NoError(err)
|
||||
assert.Equal(count, uint32(0))
|
||||
|
||||
count, err = checkKernelModules(testData, nil)
|
||||
assert.NoError(err)
|
||||
// No modules exist
|
||||
assert.Equal(count, uint32(2))
|
||||
|
||||
for module, details := range testData {
|
||||
path := filepath.Join(sysModuleDir, module)
|
||||
err = os.MkdirAll(path, testDirMode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
paramDir := filepath.Join(path, "parameters")
|
||||
err = os.MkdirAll(paramDir, testDirMode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for param, value := range details.parameters {
|
||||
paramPath := filepath.Join(paramDir, param)
|
||||
err = createFile(paramPath, value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
count, err = checkKernelModules(testData, nil)
|
||||
assert.NoError(err)
|
||||
assert.Equal(count, uint32(0))
|
||||
}
|
||||
|
||||
func TestCheckCheckKernelModulesUnreadableFile(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
if os.Geteuid() == 0 {
|
||||
t.Skip(testDisabledNeedNonRoot)
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
testData := map[string]kernelModule{
|
||||
"foo": {
|
||||
desc: "desc",
|
||||
parameters: map[string]string{
|
||||
"param1": "wibble",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
savedModInfoCmd := modInfoCmd
|
||||
savedSysModuleDir := sysModuleDir
|
||||
|
||||
// XXX: override (fake the modprobe command failing)
|
||||
modInfoCmd = "false"
|
||||
sysModuleDir = filepath.Join(dir, "sys/module")
|
||||
|
||||
defer func() {
|
||||
modInfoCmd = savedModInfoCmd
|
||||
sysModuleDir = savedSysModuleDir
|
||||
}()
|
||||
|
||||
modPath := filepath.Join(sysModuleDir, "foo/parameters")
|
||||
err = os.MkdirAll(modPath, testDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
modParamFile := filepath.Join(modPath, "param1")
|
||||
|
||||
err = createEmptyFile(modParamFile)
|
||||
assert.NoError(err)
|
||||
|
||||
// make file unreadable by non-root user
|
||||
err = os.Chmod(modParamFile, 0000)
|
||||
assert.NoError(err)
|
||||
|
||||
_, err = checkKernelModules(testData, nil)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestCheckCheckKernelModulesInvalidFileContents(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
testData := map[string]kernelModule{
|
||||
"foo": {
|
||||
desc: "desc",
|
||||
parameters: map[string]string{
|
||||
"param1": "wibble",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
savedModInfoCmd := modInfoCmd
|
||||
savedSysModuleDir := sysModuleDir
|
||||
|
||||
// XXX: override (fake the modprobe command failing)
|
||||
modInfoCmd = "false"
|
||||
sysModuleDir = filepath.Join(dir, "sys/module")
|
||||
|
||||
defer func() {
|
||||
modInfoCmd = savedModInfoCmd
|
||||
sysModuleDir = savedSysModuleDir
|
||||
}()
|
||||
|
||||
modPath := filepath.Join(sysModuleDir, "foo/parameters")
|
||||
err = os.MkdirAll(modPath, testDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
modParamFile := filepath.Join(modPath, "param1")
|
||||
|
||||
err = createFile(modParamFile, "burp")
|
||||
assert.NoError(err)
|
||||
|
||||
count, err := checkKernelModules(testData, nil)
|
||||
assert.NoError(err)
|
||||
assert.Equal(count, uint32(1))
|
||||
}
|
||||
|
||||
func TestCheckCLIFunctionFail(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
oldProcCPUInfo := procCPUInfo
|
||||
|
||||
// doesn't exist
|
||||
procCPUInfo = filepath.Join(dir, "cpuinfo")
|
||||
|
||||
defer func() {
|
||||
procCPUInfo = oldProcCPUInfo
|
||||
}()
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, nil, nil)
|
||||
app.Name = "foo"
|
||||
|
||||
fn, ok := kataCheckCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
err = fn(ctx)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestCheckKernelParamHandler(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
savedModInfoCmd := modInfoCmd
|
||||
savedSysModuleDir := sysModuleDir
|
||||
|
||||
// XXX: override (fake the modprobe command failing)
|
||||
modInfoCmd = "false"
|
||||
sysModuleDir = filepath.Join(dir, "sys/module")
|
||||
|
||||
defer func() {
|
||||
modInfoCmd = savedModInfoCmd
|
||||
sysModuleDir = savedSysModuleDir
|
||||
}()
|
||||
|
||||
handler := func(onVMM bool, fields logrus.Fields, msg string) bool {
|
||||
param, ok := fields["parameter"].(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if param == "param1" {
|
||||
return true
|
||||
}
|
||||
|
||||
// don't ignore the error
|
||||
return false
|
||||
}
|
||||
|
||||
testData1 := map[string]kernelModule{
|
||||
"foo": {
|
||||
desc: "desc",
|
||||
parameters: map[string]string{},
|
||||
},
|
||||
"bar": {
|
||||
desc: "desc",
|
||||
parameters: map[string]string{
|
||||
"param1": "hello",
|
||||
"param2": "world",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
checkKernelParamHandler(assert, testData1, testData1, handler, false, uint32(0))
|
||||
|
||||
testDataToCreate := map[string]kernelModule{
|
||||
"foo": {
|
||||
desc: "desc",
|
||||
parameters: map[string]string{
|
||||
"param1": "moo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testDataToExpect := map[string]kernelModule{
|
||||
"foo": {
|
||||
desc: "desc",
|
||||
parameters: map[string]string{
|
||||
"param1": "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Expected and actual are different, but the handler should deal with
|
||||
// the problem.
|
||||
checkKernelParamHandler(assert, testDataToCreate, testDataToExpect, handler, false, uint32(0))
|
||||
|
||||
// Expected and actual are different, so with no handler we expect a
|
||||
// single error (due to "param1"'s value being different)
|
||||
checkKernelParamHandler(assert, testDataToCreate, testDataToExpect, nil, false, uint32(1))
|
||||
}
|
365
cli/kata-env.go
Normal file
365
cli/kata-env.go
Normal file
@ -0,0 +1,365 @@
|
||||
// Copyright (c) 2017-2018 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Semantic version for the output of the command.
|
||||
//
|
||||
// XXX: Increment for every change to the output format
|
||||
// (meaning any change to the EnvInfo type).
|
||||
const formatVersion = "1.0.9"
|
||||
|
||||
// MetaInfo stores information on the format of the output itself
|
||||
type MetaInfo struct {
|
||||
// output format version
|
||||
Version string
|
||||
}
|
||||
|
||||
// KernelInfo stores kernel details
|
||||
type KernelInfo struct {
|
||||
Path string
|
||||
Parameters string
|
||||
}
|
||||
|
||||
// ImageInfo stores root filesystem image details
|
||||
type ImageInfo struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
// CPUInfo stores host CPU details
|
||||
type CPUInfo struct {
|
||||
Vendor string
|
||||
Model string
|
||||
}
|
||||
|
||||
// RuntimeConfigInfo stores runtime config details.
|
||||
type RuntimeConfigInfo struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
// RuntimeInfo stores runtime details.
|
||||
type RuntimeInfo struct {
|
||||
Version RuntimeVersionInfo
|
||||
Config RuntimeConfigInfo
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// RuntimeVersionInfo stores details of the runtime version
|
||||
type RuntimeVersionInfo struct {
|
||||
Semver string
|
||||
Commit string
|
||||
OCI string
|
||||
}
|
||||
|
||||
// HypervisorInfo stores hypervisor details
|
||||
type HypervisorInfo struct {
|
||||
MachineType string
|
||||
Version string
|
||||
Path string
|
||||
Debug bool
|
||||
BlockDeviceDriver string
|
||||
}
|
||||
|
||||
// ProxyInfo stores proxy details
|
||||
type ProxyInfo struct {
|
||||
Type string
|
||||
Version string
|
||||
Path string
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// ShimInfo stores shim details
|
||||
type ShimInfo struct {
|
||||
Type string
|
||||
Version string
|
||||
Path string
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// AgentInfo stores agent details
|
||||
type AgentInfo struct {
|
||||
Type string
|
||||
Version string
|
||||
}
|
||||
|
||||
// DistroInfo stores host operating system distribution details.
|
||||
type DistroInfo struct {
|
||||
Name string
|
||||
Version string
|
||||
}
|
||||
|
||||
// HostInfo stores host details
|
||||
type HostInfo struct {
|
||||
Kernel string
|
||||
Architecture string
|
||||
Distro DistroInfo
|
||||
CPU CPUInfo
|
||||
VMContainerCapable bool
|
||||
}
|
||||
|
||||
// EnvInfo collects all information that will be displayed by the
|
||||
// env command.
|
||||
//
|
||||
// XXX: Any changes must be coupled with a change to formatVersion.
|
||||
type EnvInfo struct {
|
||||
Meta MetaInfo
|
||||
Runtime RuntimeInfo
|
||||
Hypervisor HypervisorInfo
|
||||
Image ImageInfo
|
||||
Kernel KernelInfo
|
||||
Proxy ProxyInfo
|
||||
Shim ShimInfo
|
||||
Agent AgentInfo
|
||||
Host HostInfo
|
||||
}
|
||||
|
||||
func getMetaInfo() MetaInfo {
|
||||
return MetaInfo{
|
||||
Version: formatVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func getRuntimeInfo(configFile string, config oci.RuntimeConfig) RuntimeInfo {
|
||||
runtimeVersion := RuntimeVersionInfo{
|
||||
Semver: version,
|
||||
Commit: commit,
|
||||
OCI: specs.Version,
|
||||
}
|
||||
|
||||
runtimeConfig := RuntimeConfigInfo{
|
||||
Path: configFile,
|
||||
}
|
||||
|
||||
return RuntimeInfo{
|
||||
Version: runtimeVersion,
|
||||
Config: runtimeConfig,
|
||||
}
|
||||
}
|
||||
|
||||
func getHostInfo() (HostInfo, error) {
|
||||
hostKernelVersion, err := getKernelVersion()
|
||||
if err != nil {
|
||||
return HostInfo{}, err
|
||||
}
|
||||
|
||||
hostDistroName, hostDistroVersion, err := getDistroDetails()
|
||||
if err != nil {
|
||||
return HostInfo{}, err
|
||||
}
|
||||
|
||||
cpuVendor, cpuModel, err := getCPUDetails()
|
||||
if err != nil {
|
||||
return HostInfo{}, err
|
||||
}
|
||||
|
||||
hostVMContainerCapable := true
|
||||
|
||||
details := vmContainerCapableDetails{
|
||||
cpuInfoFile: procCPUInfo,
|
||||
requiredCPUFlags: archRequiredCPUFlags,
|
||||
requiredCPUAttribs: archRequiredCPUAttribs,
|
||||
requiredKernelModules: archRequiredKernelModules,
|
||||
}
|
||||
|
||||
if err = hostIsVMContainerCapable(details); err != nil {
|
||||
hostVMContainerCapable = false
|
||||
}
|
||||
|
||||
hostDistro := DistroInfo{
|
||||
Name: hostDistroName,
|
||||
Version: hostDistroVersion,
|
||||
}
|
||||
|
||||
hostCPU := CPUInfo{
|
||||
Vendor: cpuVendor,
|
||||
Model: cpuModel,
|
||||
}
|
||||
|
||||
host := HostInfo{
|
||||
Kernel: hostKernelVersion,
|
||||
Architecture: arch,
|
||||
Distro: hostDistro,
|
||||
CPU: hostCPU,
|
||||
VMContainerCapable: hostVMContainerCapable,
|
||||
}
|
||||
|
||||
return host, nil
|
||||
}
|
||||
|
||||
func getProxyInfo(config oci.RuntimeConfig) (ProxyInfo, error) {
|
||||
version, err := getCommandVersion(defaultProxyPath)
|
||||
if err != nil {
|
||||
version = unknown
|
||||
}
|
||||
|
||||
proxy := ProxyInfo{
|
||||
Type: string(config.ProxyType),
|
||||
Version: version,
|
||||
Path: config.ProxyConfig.Path,
|
||||
Debug: config.ProxyConfig.Debug,
|
||||
}
|
||||
|
||||
return proxy, nil
|
||||
}
|
||||
|
||||
func getCommandVersion(cmd string) (string, error) {
|
||||
return runCommand([]string{cmd, "--version"})
|
||||
}
|
||||
|
||||
func getShimInfo(config oci.RuntimeConfig) (ShimInfo, error) {
|
||||
shimConfig, ok := config.ShimConfig.(vc.ShimConfig)
|
||||
if !ok {
|
||||
return ShimInfo{}, errors.New("cannot determine shim config")
|
||||
}
|
||||
|
||||
shimPath := shimConfig.Path
|
||||
|
||||
version, err := getCommandVersion(shimPath)
|
||||
if err != nil {
|
||||
version = unknown
|
||||
}
|
||||
|
||||
shim := ShimInfo{
|
||||
Type: string(config.ShimType),
|
||||
Version: version,
|
||||
Path: shimPath,
|
||||
Debug: shimConfig.Debug,
|
||||
}
|
||||
|
||||
return shim, nil
|
||||
}
|
||||
|
||||
func getAgentInfo(config oci.RuntimeConfig) AgentInfo {
|
||||
agent := AgentInfo{
|
||||
Type: string(config.AgentType),
|
||||
Version: unknown,
|
||||
}
|
||||
|
||||
return agent
|
||||
}
|
||||
|
||||
func getHypervisorInfo(config oci.RuntimeConfig) HypervisorInfo {
|
||||
hypervisorPath := config.HypervisorConfig.HypervisorPath
|
||||
|
||||
version, err := getCommandVersion(hypervisorPath)
|
||||
if err != nil {
|
||||
version = unknown
|
||||
}
|
||||
|
||||
return HypervisorInfo{
|
||||
MachineType: config.HypervisorConfig.HypervisorMachineType,
|
||||
Version: version,
|
||||
Path: hypervisorPath,
|
||||
BlockDeviceDriver: config.HypervisorConfig.BlockDeviceDriver,
|
||||
}
|
||||
}
|
||||
|
||||
func getEnvInfo(configFile string, config oci.RuntimeConfig) (env EnvInfo, err error) {
|
||||
meta := getMetaInfo()
|
||||
|
||||
runtime := getRuntimeInfo(configFile, config)
|
||||
|
||||
host, err := getHostInfo()
|
||||
if err != nil {
|
||||
return EnvInfo{}, err
|
||||
}
|
||||
|
||||
proxy, _ := getProxyInfo(config)
|
||||
|
||||
shim, err := getShimInfo(config)
|
||||
if err != nil {
|
||||
return EnvInfo{}, err
|
||||
}
|
||||
|
||||
agent := getAgentInfo(config)
|
||||
|
||||
hypervisor := getHypervisorInfo(config)
|
||||
|
||||
image := ImageInfo{
|
||||
Path: config.HypervisorConfig.ImagePath,
|
||||
}
|
||||
|
||||
kernel := KernelInfo{
|
||||
Path: config.HypervisorConfig.KernelPath,
|
||||
Parameters: strings.Join(vc.SerializeParams(config.HypervisorConfig.KernelParams, "="), " "),
|
||||
}
|
||||
|
||||
env = EnvInfo{
|
||||
Meta: meta,
|
||||
Runtime: runtime,
|
||||
Hypervisor: hypervisor,
|
||||
Image: image,
|
||||
Kernel: kernel,
|
||||
Proxy: proxy,
|
||||
Shim: shim,
|
||||
Agent: agent,
|
||||
Host: host,
|
||||
}
|
||||
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func showSettings(env EnvInfo, file *os.File) error {
|
||||
encoder := toml.NewEncoder(file)
|
||||
|
||||
err := encoder.Encode(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleSettings(file *os.File, metadata map[string]interface{}) error {
|
||||
if file == nil {
|
||||
return errors.New("Invalid output file specified")
|
||||
}
|
||||
|
||||
configFile, ok := metadata["configFile"].(string)
|
||||
if !ok {
|
||||
return errors.New("cannot determine config file")
|
||||
}
|
||||
|
||||
runtimeConfig, ok := metadata["runtimeConfig"].(oci.RuntimeConfig)
|
||||
if !ok {
|
||||
return errors.New("cannot determine runtime config")
|
||||
}
|
||||
|
||||
env, err := getEnvInfo(configFile, runtimeConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return showSettings(env, file)
|
||||
}
|
||||
|
||||
var kataEnvCLICommand = cli.Command{
|
||||
Name: envCmd,
|
||||
Usage: "display settings",
|
||||
Action: func(context *cli.Context) error {
|
||||
return handleSettings(defaultOutputFile, context.App.Metadata)
|
||||
},
|
||||
}
|
969
cli/kata-env_test.go
Normal file
969
cli/kata-env_test.go
Normal file
@ -0,0 +1,969 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
goruntime "runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const testProxyURL = "file:///proxyURL"
|
||||
const testProxyVersion = "proxy version 0.1"
|
||||
const testShimVersion = "shim version 0.1"
|
||||
const testHypervisorVersion = "QEMU emulator version 2.7.0+git.741f430a96-6.1, Copyright (c) 2003-2016 Fabrice Bellard and the QEMU Project developers"
|
||||
|
||||
// makeVersionBinary creates a shell script with the specified file
|
||||
// name. When run as "file --version", it will display the specified
|
||||
// version to stdout and exit successfully.
|
||||
func makeVersionBinary(file, version string) error {
|
||||
err := createFile(file,
|
||||
fmt.Sprintf(`#!/bin/sh
|
||||
[ "$1" = "--version" ] && echo "%s"`, version))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Chmod(file, testExeFileMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeRuntimeConfig(prefixDir string) (configFile string, config oci.RuntimeConfig, err error) {
|
||||
const logPath = "/log/path"
|
||||
hypervisorPath := filepath.Join(prefixDir, "hypervisor")
|
||||
kernelPath := filepath.Join(prefixDir, "kernel")
|
||||
imagePath := filepath.Join(prefixDir, "image")
|
||||
kernelParams := "foo=bar xyz"
|
||||
machineType := "machineType"
|
||||
shimPath := filepath.Join(prefixDir, "shim")
|
||||
proxyPath := filepath.Join(prefixDir, "proxy")
|
||||
disableBlock := true
|
||||
blockStorageDriver := "virtio-scsi"
|
||||
|
||||
// override
|
||||
defaultProxyPath = proxyPath
|
||||
|
||||
filesToCreate := []string{
|
||||
hypervisorPath,
|
||||
kernelPath,
|
||||
imagePath,
|
||||
}
|
||||
|
||||
for _, file := range filesToCreate {
|
||||
err := createEmptyFile(file)
|
||||
if err != nil {
|
||||
return "", oci.RuntimeConfig{}, err
|
||||
}
|
||||
}
|
||||
|
||||
err = makeVersionBinary(shimPath, testShimVersion)
|
||||
if err != nil {
|
||||
return "", oci.RuntimeConfig{}, err
|
||||
}
|
||||
|
||||
err = makeVersionBinary(proxyPath, testProxyVersion)
|
||||
if err != nil {
|
||||
return "", oci.RuntimeConfig{}, err
|
||||
}
|
||||
|
||||
err = makeVersionBinary(hypervisorPath, testHypervisorVersion)
|
||||
if err != nil {
|
||||
return "", oci.RuntimeConfig{}, err
|
||||
}
|
||||
|
||||
runtimeConfig := makeRuntimeConfigFileData(
|
||||
"qemu",
|
||||
hypervisorPath,
|
||||
kernelPath,
|
||||
imagePath,
|
||||
kernelParams,
|
||||
machineType,
|
||||
shimPath,
|
||||
testProxyURL,
|
||||
logPath,
|
||||
disableBlock,
|
||||
blockStorageDriver)
|
||||
|
||||
configFile = path.Join(prefixDir, "runtime.toml")
|
||||
err = createConfig(configFile, runtimeConfig)
|
||||
if err != nil {
|
||||
return "", oci.RuntimeConfig{}, err
|
||||
}
|
||||
|
||||
_, config, err = loadConfiguration(configFile, true)
|
||||
if err != nil {
|
||||
return "", oci.RuntimeConfig{}, err
|
||||
}
|
||||
|
||||
return configFile, config, nil
|
||||
}
|
||||
|
||||
func getExpectedProxyDetails(config oci.RuntimeConfig) (ProxyInfo, error) {
|
||||
return ProxyInfo{
|
||||
Type: string(config.ProxyType),
|
||||
Version: testProxyVersion,
|
||||
Path: config.ProxyConfig.Path,
|
||||
Debug: config.ProxyConfig.Debug,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getExpectedShimDetails(config oci.RuntimeConfig) (ShimInfo, error) {
|
||||
shimConfig, ok := config.ShimConfig.(vc.ShimConfig)
|
||||
if !ok {
|
||||
return ShimInfo{}, fmt.Errorf("failed to get shim config")
|
||||
}
|
||||
|
||||
shimPath := shimConfig.Path
|
||||
|
||||
return ShimInfo{
|
||||
Type: string(config.ShimType),
|
||||
Version: testShimVersion,
|
||||
Path: shimPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getExpectedAgentDetails(config oci.RuntimeConfig) (AgentInfo, error) {
|
||||
return AgentInfo{
|
||||
Type: string(config.AgentType),
|
||||
Version: unknown,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getExpectedHostDetails(tmpdir string) (HostInfo, error) {
|
||||
type filesToCreate struct {
|
||||
file string
|
||||
contents string
|
||||
}
|
||||
|
||||
const expectedKernelVersion = "99.1"
|
||||
const expectedArch = goruntime.GOARCH
|
||||
|
||||
expectedDistro := DistroInfo{
|
||||
Name: "Foo",
|
||||
Version: "42",
|
||||
}
|
||||
|
||||
expectedCPU := CPUInfo{
|
||||
Vendor: "moi",
|
||||
Model: "awesome XI",
|
||||
}
|
||||
|
||||
expectedHostDetails := HostInfo{
|
||||
Kernel: expectedKernelVersion,
|
||||
Architecture: expectedArch,
|
||||
Distro: expectedDistro,
|
||||
CPU: expectedCPU,
|
||||
VMContainerCapable: false,
|
||||
}
|
||||
|
||||
testProcCPUInfo := filepath.Join(tmpdir, "cpuinfo")
|
||||
testOSRelease := filepath.Join(tmpdir, "os-release")
|
||||
|
||||
// XXX: This file is *NOT* created by this function on purpose
|
||||
// (to ensure the only file checked by the tests is
|
||||
// testOSRelease). osReleaseClr handling is tested in
|
||||
// utils_test.go.
|
||||
testOSReleaseClr := filepath.Join(tmpdir, "os-release-clr")
|
||||
|
||||
testProcVersion := filepath.Join(tmpdir, "proc-version")
|
||||
|
||||
// override
|
||||
procVersion = testProcVersion
|
||||
osRelease = testOSRelease
|
||||
osReleaseClr = testOSReleaseClr
|
||||
procCPUInfo = testProcCPUInfo
|
||||
|
||||
procVersionContents := fmt.Sprintf("Linux version %s a b c",
|
||||
expectedKernelVersion)
|
||||
|
||||
osReleaseContents := fmt.Sprintf(`
|
||||
NAME="%s"
|
||||
VERSION_ID="%s"
|
||||
`, expectedDistro.Name, expectedDistro.Version)
|
||||
|
||||
procCPUInfoContents := fmt.Sprintf(`
|
||||
vendor_id : %s
|
||||
model name : %s
|
||||
`, expectedCPU.Vendor, expectedCPU.Model)
|
||||
|
||||
data := []filesToCreate{
|
||||
{procVersion, procVersionContents},
|
||||
{osRelease, osReleaseContents},
|
||||
{procCPUInfo, procCPUInfoContents},
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
err := createFile(d.file, d.contents)
|
||||
if err != nil {
|
||||
return HostInfo{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return expectedHostDetails, nil
|
||||
}
|
||||
|
||||
func getExpectedHypervisor(config oci.RuntimeConfig) HypervisorInfo {
|
||||
return HypervisorInfo{
|
||||
Version: testHypervisorVersion,
|
||||
Path: config.HypervisorConfig.HypervisorPath,
|
||||
MachineType: config.HypervisorConfig.HypervisorMachineType,
|
||||
BlockDeviceDriver: config.HypervisorConfig.BlockDeviceDriver,
|
||||
}
|
||||
}
|
||||
|
||||
func getExpectedImage(config oci.RuntimeConfig) ImageInfo {
|
||||
return ImageInfo{
|
||||
Path: config.HypervisorConfig.ImagePath,
|
||||
}
|
||||
}
|
||||
|
||||
func getExpectedKernel(config oci.RuntimeConfig) KernelInfo {
|
||||
return KernelInfo{
|
||||
Path: config.HypervisorConfig.KernelPath,
|
||||
Parameters: strings.Join(vc.SerializeParams(config.HypervisorConfig.KernelParams, "="), " "),
|
||||
}
|
||||
}
|
||||
|
||||
func getExpectedRuntimeDetails(configFile string) RuntimeInfo {
|
||||
return RuntimeInfo{
|
||||
Version: RuntimeVersionInfo{
|
||||
Semver: version,
|
||||
Commit: commit,
|
||||
OCI: specs.Version,
|
||||
},
|
||||
Config: RuntimeConfigInfo{
|
||||
Path: configFile,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getExpectedSettings(config oci.RuntimeConfig, tmpdir, configFile string) (EnvInfo, error) {
|
||||
meta := getExpectedMetaInfo()
|
||||
|
||||
runtime := getExpectedRuntimeDetails(configFile)
|
||||
|
||||
proxy, err := getExpectedProxyDetails(config)
|
||||
if err != nil {
|
||||
return EnvInfo{}, err
|
||||
}
|
||||
|
||||
shim, err := getExpectedShimDetails(config)
|
||||
if err != nil {
|
||||
return EnvInfo{}, err
|
||||
}
|
||||
|
||||
agent, err := getExpectedAgentDetails(config)
|
||||
if err != nil {
|
||||
return EnvInfo{}, err
|
||||
}
|
||||
|
||||
host, err := getExpectedHostDetails(tmpdir)
|
||||
if err != nil {
|
||||
return EnvInfo{}, err
|
||||
}
|
||||
|
||||
hypervisor := getExpectedHypervisor(config)
|
||||
kernel := getExpectedKernel(config)
|
||||
image := getExpectedImage(config)
|
||||
|
||||
env := EnvInfo{
|
||||
Meta: meta,
|
||||
Runtime: runtime,
|
||||
Hypervisor: hypervisor,
|
||||
Image: image,
|
||||
Kernel: kernel,
|
||||
Proxy: proxy,
|
||||
Shim: shim,
|
||||
Agent: agent,
|
||||
Host: host,
|
||||
}
|
||||
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func getExpectedMetaInfo() MetaInfo {
|
||||
return MetaInfo{
|
||||
Version: formatVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvGetMetaInfo(t *testing.T) {
|
||||
expectedMeta := getExpectedMetaInfo()
|
||||
|
||||
meta := getMetaInfo()
|
||||
|
||||
assert.Equal(t, expectedMeta, meta)
|
||||
}
|
||||
|
||||
func TestEnvGetHostInfo(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
expectedHostDetails, err := getExpectedHostDetails(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
host, err := getHostInfo()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedHostDetails, host)
|
||||
}
|
||||
|
||||
func TestEnvGetHostInfoNoProcCPUInfo(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
_, err = getExpectedHostDetails(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.Remove(procCPUInfo)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getHostInfo()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvGetHostInfoNoOSRelease(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
_, err = getExpectedHostDetails(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.Remove(osRelease)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getHostInfo()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvGetHostInfoNoProcVersion(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
_, err = getExpectedHostDetails(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.Remove(procVersion)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getHostInfo()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvGetEnvInfo(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configFile, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedEnv, err := getExpectedSettings(config, tmpdir, configFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
env, err := getEnvInfo(configFile, config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedEnv, env)
|
||||
}
|
||||
|
||||
func TestEnvGetEnvInfoNoHypervisorVersion(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configFile, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(err)
|
||||
|
||||
expectedEnv, err := getExpectedSettings(config, tmpdir, configFile)
|
||||
assert.NoError(err)
|
||||
|
||||
err = os.Remove(config.HypervisorConfig.HypervisorPath)
|
||||
assert.NoError(err)
|
||||
|
||||
expectedEnv.Hypervisor.Version = unknown
|
||||
|
||||
env, err := getEnvInfo(configFile, config)
|
||||
assert.NoError(err)
|
||||
|
||||
assert.Equal(expectedEnv, env)
|
||||
}
|
||||
|
||||
func TestEnvGetEnvInfoShimError(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configFile, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(err)
|
||||
|
||||
config.ShimConfig = "invalid shim config"
|
||||
|
||||
_, err = getEnvInfo(configFile, config)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestEnvGetEnvInfoAgentError(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configFile, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(err)
|
||||
|
||||
config.AgentConfig = "invalid agent config"
|
||||
|
||||
_, err = getEnvInfo(configFile, config)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestEnvGetEnvInfoNoOSRelease(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configFile, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getExpectedSettings(config, tmpdir, configFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.Remove(osRelease)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getEnvInfo(configFile, config)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvGetEnvInfoNoProcCPUInfo(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configFile, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getExpectedSettings(config, tmpdir, configFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.Remove(procCPUInfo)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getEnvInfo(configFile, config)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvGetEnvInfoNoProcVersion(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configFile, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getExpectedSettings(config, tmpdir, configFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.Remove(procVersion)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getEnvInfo(configFile, config)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvGetRuntimeInfo(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configFile, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedRuntime := getExpectedRuntimeDetails(configFile)
|
||||
|
||||
runtime := getRuntimeInfo(configFile, config)
|
||||
|
||||
assert.Equal(t, expectedRuntime, runtime)
|
||||
}
|
||||
|
||||
func TestEnvGetProxyInfo(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
_, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedProxy, err := getExpectedProxyDetails(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
proxy, err := getProxyInfo(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedProxy, proxy)
|
||||
}
|
||||
|
||||
func TestEnvGetProxyInfoNoVersion(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
_, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedProxy, err := getExpectedProxyDetails(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// remove the proxy ensuring its version cannot be queried
|
||||
err = os.Remove(defaultProxyPath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedProxy.Version = unknown
|
||||
|
||||
proxy, err := getProxyInfo(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedProxy, proxy)
|
||||
}
|
||||
|
||||
func TestEnvGetShimInfo(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
_, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedShim, err := getExpectedShimDetails(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
shim, err := getShimInfo(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedShim, shim)
|
||||
}
|
||||
|
||||
func TestEnvGetShimInfoNoVersion(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
_, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedShim, err := getExpectedShimDetails(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
shimPath := expectedShim.Path
|
||||
|
||||
// ensure querying the shim version fails
|
||||
err = createFile(shimPath, `#!/bin/sh
|
||||
exit 1`)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedShim.Version = unknown
|
||||
|
||||
shim, err := getShimInfo(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedShim, shim)
|
||||
}
|
||||
|
||||
func TestEnvGetShimInfoInvalidType(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
_, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getExpectedShimDetails(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
config.ShimConfig = "foo"
|
||||
_, err = getShimInfo(config)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvGetAgentInfo(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
_, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedAgent, err := getExpectedAgentDetails(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
agent := getAgentInfo(config)
|
||||
assert.Equal(t, expectedAgent, agent)
|
||||
}
|
||||
|
||||
func testEnvShowSettings(t *testing.T, tmpdir string, tmpfile *os.File) error {
|
||||
|
||||
runtime := RuntimeInfo{}
|
||||
|
||||
hypervisor := HypervisorInfo{
|
||||
Path: "/resolved/hypervisor/path",
|
||||
MachineType: "hypervisor-machine-type",
|
||||
}
|
||||
|
||||
image := ImageInfo{
|
||||
Path: "/resolved/image/path",
|
||||
}
|
||||
|
||||
kernel := KernelInfo{
|
||||
Path: "/kernel/path",
|
||||
Parameters: "foo=bar xyz",
|
||||
}
|
||||
|
||||
proxy := ProxyInfo{
|
||||
Type: "proxy-type",
|
||||
Version: "proxy-version",
|
||||
Path: "file:///proxy-url",
|
||||
Debug: false,
|
||||
}
|
||||
|
||||
shim := ShimInfo{
|
||||
Type: "shim-type",
|
||||
Version: "shim-version",
|
||||
Path: "/resolved/shim/path",
|
||||
}
|
||||
|
||||
agent := AgentInfo{
|
||||
Type: "agent-type",
|
||||
Version: "agent-version",
|
||||
}
|
||||
|
||||
expectedHostDetails, err := getExpectedHostDetails(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
env := EnvInfo{
|
||||
Runtime: runtime,
|
||||
Hypervisor: hypervisor,
|
||||
Image: image,
|
||||
Kernel: kernel,
|
||||
Proxy: proxy,
|
||||
Shim: shim,
|
||||
Agent: agent,
|
||||
Host: expectedHostDetails,
|
||||
}
|
||||
|
||||
err = showSettings(env, tmpfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contents, err := getFileContents(tmpfile.Name())
|
||||
assert.NoError(t, err)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
encoder := toml.NewEncoder(buf)
|
||||
err = encoder.Encode(env)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedContents := buf.String()
|
||||
|
||||
assert.Equal(t, expectedContents, contents)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestEnvShowSettings(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
tmpfile, err := ioutil.TempFile("", "envShowSettings-")
|
||||
assert.NoError(t, err)
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
err = testEnvShowSettings(t, tmpdir, tmpfile)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEnvShowSettingsInvalidFile(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
tmpfile, err := ioutil.TempFile("", "envShowSettings-")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// close the file
|
||||
tmpfile.Close()
|
||||
|
||||
err = testEnvShowSettings(t, tmpdir, tmpfile)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvHandleSettings(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configFile, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getExpectedSettings(config, tmpdir, configFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
m := map[string]interface{}{
|
||||
"configFile": configFile,
|
||||
"runtimeConfig": config,
|
||||
}
|
||||
|
||||
tmpfile, err := ioutil.TempFile("", "")
|
||||
assert.NoError(t, err)
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
err = handleSettings(tmpfile, m)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var env EnvInfo
|
||||
|
||||
_, err = toml.DecodeFile(tmpfile.Name(), &env)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEnvHandleSettingsInvalidShimConfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configFile, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(err)
|
||||
|
||||
_, err = getExpectedSettings(config, tmpdir, configFile)
|
||||
assert.NoError(err)
|
||||
|
||||
config.ShimConfig = "invalid shim config"
|
||||
|
||||
m := map[string]interface{}{
|
||||
"configFile": configFile,
|
||||
"runtimeConfig": config,
|
||||
}
|
||||
|
||||
tmpfile, err := ioutil.TempFile("", "")
|
||||
assert.NoError(err)
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
err = handleSettings(tmpfile, m)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestEnvHandleSettingsInvalidParams(t *testing.T) {
|
||||
err := handleSettings(nil, map[string]interface{}{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvHandleSettingsEmptyMap(t *testing.T) {
|
||||
err := handleSettings(os.Stdout, map[string]interface{}{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvHandleSettingsInvalidFile(t *testing.T) {
|
||||
m := map[string]interface{}{
|
||||
"configFile": "foo",
|
||||
"runtimeConfig": oci.RuntimeConfig{},
|
||||
}
|
||||
|
||||
err := handleSettings(nil, m)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvHandleSettingsInvalidConfigFileType(t *testing.T) {
|
||||
m := map[string]interface{}{
|
||||
"configFile": 123,
|
||||
"runtimeConfig": oci.RuntimeConfig{},
|
||||
}
|
||||
|
||||
err := handleSettings(os.Stderr, m)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvHandleSettingsInvalidRuntimeConfigType(t *testing.T) {
|
||||
m := map[string]interface{}{
|
||||
"configFile": "/some/where",
|
||||
"runtimeConfig": true,
|
||||
}
|
||||
|
||||
err := handleSettings(os.Stderr, m)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvCLIFunction(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configFile, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getExpectedSettings(config, tmpdir, configFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, nil, nil)
|
||||
app.Name = "foo"
|
||||
|
||||
ctx.App.Metadata = map[string]interface{}{
|
||||
"configFile": configFile,
|
||||
"runtimeConfig": config,
|
||||
}
|
||||
|
||||
fn, ok := kataEnvCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(t, ok)
|
||||
|
||||
devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0666)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// throw away output
|
||||
savedOutputFile := defaultOutputFile
|
||||
defaultOutputFile = devNull
|
||||
|
||||
defer func() {
|
||||
defaultOutputFile = savedOutputFile
|
||||
}()
|
||||
|
||||
err = fn(ctx)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEnvCLIFunctionFail(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configFile, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getExpectedSettings(config, tmpdir, configFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, nil, nil)
|
||||
app.Name = "foo"
|
||||
|
||||
ctx.App.Metadata = map[string]interface{}{
|
||||
"configFile": configFile,
|
||||
"runtimeConfig": config,
|
||||
}
|
||||
|
||||
fn, ok := kataEnvCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(t, ok)
|
||||
|
||||
savedOutputFile := defaultOutputFile
|
||||
// invalidate
|
||||
defaultOutputFile = nil
|
||||
|
||||
defer func() {
|
||||
defaultOutputFile = savedOutputFile
|
||||
}()
|
||||
|
||||
err = fn(ctx)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetHypervisorInfo(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
_, config, err := makeRuntimeConfig(tmpdir)
|
||||
assert.NoError(err)
|
||||
|
||||
info := getHypervisorInfo(config)
|
||||
assert.Equal(info.Version, testHypervisorVersion)
|
||||
|
||||
err = os.Remove(config.HypervisorConfig.HypervisorPath)
|
||||
assert.NoError(err)
|
||||
|
||||
info = getHypervisorInfo(config)
|
||||
assert.Equal(info.Version, unknown)
|
||||
}
|
159
cli/kill.go
Normal file
159
cli/kill.go
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2014,2015,2016 Docker, Inc.
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var killCLICommand = cli.Command{
|
||||
Name: "kill",
|
||||
Usage: "Kill sends signals to the container's init process",
|
||||
ArgsUsage: `<container-id> [signal]
|
||||
|
||||
<container-id> is the name for the instance of the container
|
||||
[signal] is the signal to be sent to the init process (default: SIGTERM)
|
||||
|
||||
EXAMPLE:
|
||||
If the container id is "ubuntu01" the following will send a "KILL" signal
|
||||
to the init process of the "ubuntu01" container:
|
||||
|
||||
# ` + name + ` kill ubuntu01 KILL`,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "all, a",
|
||||
Usage: "send the specified signal to all processes inside the container",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
args := context.Args()
|
||||
if args.Present() == false {
|
||||
return fmt.Errorf("Missing container ID")
|
||||
}
|
||||
|
||||
// If signal is provided, it has to be the second argument.
|
||||
signal := args.Get(1)
|
||||
if signal == "" {
|
||||
signal = "SIGTERM"
|
||||
}
|
||||
|
||||
return kill(args.First(), signal, context.Bool("all"))
|
||||
},
|
||||
}
|
||||
|
||||
var signals = map[string]syscall.Signal{
|
||||
"SIGABRT": syscall.SIGABRT,
|
||||
"SIGALRM": syscall.SIGALRM,
|
||||
"SIGBUS": syscall.SIGBUS,
|
||||
"SIGCHLD": syscall.SIGCHLD,
|
||||
"SIGCLD": syscall.SIGCLD,
|
||||
"SIGCONT": syscall.SIGCONT,
|
||||
"SIGFPE": syscall.SIGFPE,
|
||||
"SIGHUP": syscall.SIGHUP,
|
||||
"SIGILL": syscall.SIGILL,
|
||||
"SIGINT": syscall.SIGINT,
|
||||
"SIGIO": syscall.SIGIO,
|
||||
"SIGIOT": syscall.SIGIOT,
|
||||
"SIGKILL": syscall.SIGKILL,
|
||||
"SIGPIPE": syscall.SIGPIPE,
|
||||
"SIGPOLL": syscall.SIGPOLL,
|
||||
"SIGPROF": syscall.SIGPROF,
|
||||
"SIGPWR": syscall.SIGPWR,
|
||||
"SIGQUIT": syscall.SIGQUIT,
|
||||
"SIGSEGV": syscall.SIGSEGV,
|
||||
"SIGSTKFLT": syscall.SIGSTKFLT,
|
||||
"SIGSTOP": syscall.SIGSTOP,
|
||||
"SIGSYS": syscall.SIGSYS,
|
||||
"SIGTERM": syscall.SIGTERM,
|
||||
"SIGTRAP": syscall.SIGTRAP,
|
||||
"SIGTSTP": syscall.SIGTSTP,
|
||||
"SIGTTIN": syscall.SIGTTIN,
|
||||
"SIGTTOU": syscall.SIGTTOU,
|
||||
"SIGUNUSED": syscall.SIGUNUSED,
|
||||
"SIGURG": syscall.SIGURG,
|
||||
"SIGUSR1": syscall.SIGUSR1,
|
||||
"SIGUSR2": syscall.SIGUSR2,
|
||||
"SIGVTALRM": syscall.SIGVTALRM,
|
||||
"SIGWINCH": syscall.SIGWINCH,
|
||||
"SIGXCPU": syscall.SIGXCPU,
|
||||
"SIGXFSZ": syscall.SIGXFSZ,
|
||||
}
|
||||
|
||||
func kill(containerID, signal string, all bool) error {
|
||||
// Checks the MUST and MUST NOT from OCI runtime specification
|
||||
status, podID, err := getExistingContainerInfo(containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containerID = status.ID
|
||||
|
||||
signum, err := processSignal(signal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// container MUST be created or running
|
||||
if status.State.State != vc.StateReady && status.State.State != vc.StateRunning {
|
||||
return fmt.Errorf("Container %s not ready or running, cannot send a signal", containerID)
|
||||
}
|
||||
|
||||
if err := vci.KillContainer(podID, containerID, signum, all); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if signum != syscall.SIGKILL && signum != syscall.SIGTERM {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = vci.StopContainer(podID, containerID)
|
||||
return err
|
||||
}
|
||||
|
||||
func processSignal(signal string) (syscall.Signal, error) {
|
||||
signum, signalOk := signals[signal]
|
||||
if signalOk {
|
||||
return signum, nil
|
||||
}
|
||||
|
||||
// Support for short name signals (INT)
|
||||
signum, signalOk = signals["SIG"+signal]
|
||||
if signalOk {
|
||||
return signum, nil
|
||||
}
|
||||
|
||||
// Support for numeric signals
|
||||
s, err := strconv.Atoi(signal)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Failed to convert signal %s to int", signal)
|
||||
}
|
||||
|
||||
signum = syscall.Signal(s)
|
||||
// Check whether signal is valid or not
|
||||
for _, sig := range signals {
|
||||
if sig == signum {
|
||||
// signal is a valid signal
|
||||
return signum, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("Signal %s is not supported", signal)
|
||||
}
|
280
cli/kill_test.go
Normal file
280
cli/kill_test.go
Normal file
@ -0,0 +1,280 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/vcMock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
testKillContainerFuncReturnNil = func(podID, containerID string, signal syscall.Signal, all bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
testStopContainerFuncReturnNil = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
return &vcMock.Container{}, nil
|
||||
}
|
||||
)
|
||||
|
||||
func TestProcessSignal(t *testing.T) {
|
||||
tests := []struct {
|
||||
signal string
|
||||
valid bool
|
||||
signum syscall.Signal
|
||||
}{
|
||||
{"SIGDCKBY", false, 0}, //invalid signal
|
||||
{"DCKBY", false, 0}, //invalid signal
|
||||
{"99999", false, 0}, //invalid signal
|
||||
{"SIGTERM", true, syscall.SIGTERM},
|
||||
{"TERM", true, syscall.SIGTERM},
|
||||
{"15", true, syscall.SIGTERM},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
signum, err := processSignal(test.signal)
|
||||
if signum != test.signum {
|
||||
t.Fatalf("signal received: %d expected signal: %d\n", signum, test.signum)
|
||||
}
|
||||
if test.valid && err != nil {
|
||||
t.Fatalf("signal %s is a valid but a error was received: %s\n", test.signal, err)
|
||||
}
|
||||
if !test.valid && err == nil {
|
||||
t.Fatalf("signal %s is not a valid signal and no error was reported\n", test.signal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testKillCLIFunctionTerminationSignalSuccessful(t *testing.T, sig string) {
|
||||
assert := assert.New(t)
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
|
||||
testingImpl.StopContainerFunc = testStopContainerFuncReturnNil
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.KillContainerFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID, sig})
|
||||
|
||||
execCLICommandFunc(assert, killCLICommand, set, false)
|
||||
}
|
||||
|
||||
func TestKillCLIFunctionSigkillSuccessful(t *testing.T) {
|
||||
testKillCLIFunctionTerminationSignalSuccessful(t, "SIGKILL")
|
||||
}
|
||||
|
||||
func TestKillCLIFunctionSigtermSuccessful(t *testing.T) {
|
||||
testKillCLIFunctionTerminationSignalSuccessful(t, "SIGTERM")
|
||||
}
|
||||
|
||||
func TestKillCLIFunctionNotTerminationSignalSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.KillContainerFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID, "SIGUSR1"})
|
||||
|
||||
execCLICommandFunc(assert, killCLICommand, set, false)
|
||||
}
|
||||
|
||||
func TestKillCLIFunctionNoSignalSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.KillContainerFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID})
|
||||
|
||||
execCLICommandFunc(assert, killCLICommand, set, false)
|
||||
}
|
||||
|
||||
func TestKillCLIFunctionEnableAllSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.KillContainerFunc = func(podID, containerID string, signal syscall.Signal, all bool) error {
|
||||
if !all {
|
||||
return fmt.Errorf("Expecting -all flag = true, Got false")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.KillContainerFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Bool("all", true, "")
|
||||
set.Parse([]string{testContainerID})
|
||||
|
||||
execCLICommandFunc(assert, killCLICommand, set, false)
|
||||
}
|
||||
|
||||
func TestKillCLIFunctionNoContainerIDFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
|
||||
execCLICommandFunc(assert, killCLICommand, set, true)
|
||||
}
|
||||
|
||||
func TestKillCLIFunctionContainerNotExistFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.KillContainerFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID})
|
||||
|
||||
execCLICommandFunc(assert, killCLICommand, set, true)
|
||||
}
|
||||
|
||||
func TestKillCLIFunctionInvalidSignalFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.KillContainerFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID, "SIGINVALID"})
|
||||
|
||||
execCLICommandFunc(assert, killCLICommand, set, true)
|
||||
}
|
||||
|
||||
func TestKillCLIFunctionInvalidStatePausedFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StatePaused,
|
||||
}
|
||||
|
||||
testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.KillContainerFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID})
|
||||
|
||||
execCLICommandFunc(assert, killCLICommand, set, true)
|
||||
}
|
||||
|
||||
func TestKillCLIFunctionInvalidStateStoppedFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateStopped,
|
||||
}
|
||||
|
||||
testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.KillContainerFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID})
|
||||
|
||||
execCLICommandFunc(assert, killCLICommand, set, true)
|
||||
}
|
||||
|
||||
func TestKillCLIFunctionKillContainerFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID})
|
||||
|
||||
execCLICommandFunc(assert, killCLICommand, set, true)
|
||||
}
|
392
cli/list.go
Normal file
392
cli/list.go
Normal file
@ -0,0 +1,392 @@
|
||||
// Copyright (c) 2014,2015,2016,2017 Docker, Inc.
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
oci "github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
)
|
||||
|
||||
const formatOptions = `table or json`
|
||||
|
||||
// containerState represents the platform agnostic pieces relating to a
|
||||
// running container's status and state
|
||||
type containerState struct {
|
||||
// Version is the OCI version for the container
|
||||
Version string `json:"ociVersion"`
|
||||
// ID is the container ID
|
||||
ID string `json:"id"`
|
||||
// InitProcessPid is the init process id in the parent namespace
|
||||
InitProcessPid int `json:"pid"`
|
||||
// Status is the current status of the container, running, paused, ...
|
||||
Status string `json:"status"`
|
||||
// Bundle is the path on the filesystem to the bundle
|
||||
Bundle string `json:"bundle"`
|
||||
// Rootfs is a path to a directory containing the container's root filesystem.
|
||||
Rootfs string `json:"rootfs"`
|
||||
// Created is the unix timestamp for the creation time of the container in UTC
|
||||
Created time.Time `json:"created"`
|
||||
// Annotations is the user defined annotations added to the config.
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
// The owner of the state directory (the owner of the container).
|
||||
Owner string `json:"owner"`
|
||||
}
|
||||
|
||||
type asset struct {
|
||||
Path string `json:"path"`
|
||||
Custom bool `json:"bool"`
|
||||
}
|
||||
|
||||
// hypervisorDetails stores details of the hypervisor used to host
|
||||
// the container
|
||||
type hypervisorDetails struct {
|
||||
HypervisorAsset asset `json:"hypervisorAsset"`
|
||||
ImageAsset asset `json:"imageAsset"`
|
||||
KernelAsset asset `json:"kernelAsset"`
|
||||
}
|
||||
|
||||
// fullContainerState specifies the core state plus the hypervisor
|
||||
// details
|
||||
type fullContainerState struct {
|
||||
containerState
|
||||
CurrentHypervisorDetails hypervisorDetails `json:"currentHypervisor"`
|
||||
LatestHypervisorDetails hypervisorDetails `json:"latestHypervisor"`
|
||||
StaleAssets []string
|
||||
}
|
||||
|
||||
type formatState interface {
|
||||
Write(state []fullContainerState, showAll bool, file *os.File) error
|
||||
}
|
||||
|
||||
type formatJSON struct{}
|
||||
type formatIDList struct{}
|
||||
type formatTabular struct{}
|
||||
|
||||
var listCLICommand = cli.Command{
|
||||
Name: "list",
|
||||
Usage: "lists containers started by " + name + " with the given root",
|
||||
ArgsUsage: `
|
||||
|
||||
Where the given root is specified via the global option "--root"
|
||||
(default: "` + defaultRootDirectory + `").
|
||||
|
||||
EXAMPLE 1:
|
||||
To list containers created via the default "--root":
|
||||
# ` + name + ` list
|
||||
|
||||
EXAMPLE 2:
|
||||
To list containers created using a non-default value for "--root":
|
||||
# ` + name + ` --root value list`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format, f",
|
||||
Value: "table",
|
||||
Usage: `select one of: ` + formatOptions,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "display only container IDs",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "kata-all",
|
||||
Usage: "display all available " + project + " information",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
s, err := getContainers(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file := defaultOutputFile
|
||||
showAll := context.Bool("kata-all")
|
||||
|
||||
var fs formatState = formatIDList{}
|
||||
|
||||
if context.Bool("quiet") {
|
||||
fs = formatIDList{}
|
||||
} else {
|
||||
|
||||
switch context.String("format") {
|
||||
case "table":
|
||||
fs = formatTabular{}
|
||||
|
||||
case "json":
|
||||
fs = formatJSON{}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid format option")
|
||||
}
|
||||
}
|
||||
|
||||
return fs.Write(s, showAll, file)
|
||||
},
|
||||
}
|
||||
|
||||
// getStaleAssetsreturns compares the two specified hypervisorDetails objects
|
||||
// and returns a list of strings representing which assets in "old" are not
|
||||
// current compared to "new". If old and new are identical, the empty string
|
||||
// will be returned.
|
||||
//
|
||||
// Notes:
|
||||
//
|
||||
// - This function is trivial because it relies upon the fact that new
|
||||
// containers are always created with the latest versions of all assets.
|
||||
//
|
||||
// - WARNING: Since this function only compares local values, it is unable to
|
||||
// determine if newer (remote) assets are available.
|
||||
func getStaleAssets(old, new hypervisorDetails) []string {
|
||||
var stale []string
|
||||
|
||||
if old.KernelAsset.Path != new.KernelAsset.Path {
|
||||
if old.KernelAsset.Custom {
|
||||
// The workload kernel asset is a custom one, i.e. it's not coming
|
||||
// from the runtime configuration file. Thus it does not make sense
|
||||
// to compare it against the configured kernel asset.
|
||||
// We assume a custom kernel asset has been updated if the
|
||||
// corresponding path no longer exists, i.e. it's been replaced by
|
||||
// a new kernel, e.g. with a new version name.
|
||||
// Replacing a custom kernel asset binary with exactly the same
|
||||
// binary name won't allow us to detect if it's staled or not.
|
||||
if _, err := os.Stat(old.KernelAsset.Path); os.IsNotExist(err) {
|
||||
stale = append(stale, "kernel")
|
||||
}
|
||||
} else {
|
||||
stale = append(stale, "kernel")
|
||||
}
|
||||
}
|
||||
|
||||
if old.ImageAsset.Path != new.ImageAsset.Path {
|
||||
if old.ImageAsset.Custom {
|
||||
// The workload image asset is a custom one, i.e. it's not coming
|
||||
// from the runtime configuration file. Thus it does not make sense
|
||||
// to compare it against the configured image asset.
|
||||
// We assume a custom image asset has been updated if the
|
||||
// corresponding path no longer exists, i.e. it's been replaced by
|
||||
// a new image, e.g. with a new version name.
|
||||
// Replacing a custom image asset binary with exactly the same
|
||||
// binary name won't allow us to detect if it's staled or not.
|
||||
if _, err := os.Stat(old.ImageAsset.Path); os.IsNotExist(err) {
|
||||
stale = append(stale, "image")
|
||||
}
|
||||
} else {
|
||||
stale = append(stale, "image")
|
||||
}
|
||||
}
|
||||
|
||||
return stale
|
||||
}
|
||||
|
||||
func (f formatIDList) Write(state []fullContainerState, showAll bool, file *os.File) error {
|
||||
for _, item := range state {
|
||||
_, err := fmt.Fprintln(file, item.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f formatTabular) Write(state []fullContainerState, showAll bool, file *os.File) error {
|
||||
// values used by runc
|
||||
flags := uint(0)
|
||||
minWidth := 12
|
||||
tabWidth := 1
|
||||
padding := 3
|
||||
|
||||
w := tabwriter.NewWriter(file, minWidth, tabWidth, padding, ' ', flags)
|
||||
|
||||
fmt.Fprint(w, "ID\tPID\tSTATUS\tBUNDLE\tCREATED\tOWNER")
|
||||
|
||||
if showAll {
|
||||
fmt.Fprint(w, "\tHYPERVISOR\tKERNEL\tIMAGE\tLATEST-KERNEL\tLATEST-IMAGE\tSTALE\n")
|
||||
} else {
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
|
||||
for _, item := range state {
|
||||
fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s",
|
||||
item.ID,
|
||||
item.InitProcessPid,
|
||||
item.Status,
|
||||
item.Bundle,
|
||||
item.Created.Format(time.RFC3339Nano),
|
||||
item.Owner)
|
||||
|
||||
if showAll {
|
||||
stale := strings.Join(item.StaleAssets, ",")
|
||||
if stale == "" {
|
||||
stale = "-"
|
||||
}
|
||||
|
||||
current := item.CurrentHypervisorDetails
|
||||
latest := item.LatestHypervisorDetails
|
||||
|
||||
all := fmt.Sprintf("\t%s\t%s\t%s",
|
||||
current.HypervisorAsset.Path,
|
||||
current.KernelAsset.Path,
|
||||
current.ImageAsset.Path)
|
||||
|
||||
if !current.KernelAsset.Custom {
|
||||
all += fmt.Sprintf("\t%s", latest.KernelAsset.Path)
|
||||
} else {
|
||||
all += fmt.Sprintf("\t%s", current.KernelAsset.Path)
|
||||
}
|
||||
|
||||
if !current.ImageAsset.Custom {
|
||||
all += fmt.Sprintf("\t%s", latest.ImageAsset.Path)
|
||||
} else {
|
||||
all += fmt.Sprintf("\t%s", current.ImageAsset.Path)
|
||||
}
|
||||
|
||||
all += fmt.Sprintf("\t%s\n", stale)
|
||||
|
||||
fmt.Fprint(w, all)
|
||||
} else {
|
||||
fmt.Fprint(w, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func (f formatJSON) Write(state []fullContainerState, showAll bool, file *os.File) error {
|
||||
return json.NewEncoder(file).Encode(state)
|
||||
}
|
||||
|
||||
// getDirOwner returns the UID of the specified directory
|
||||
func getDirOwner(dir string) (uint32, error) {
|
||||
if dir == "" {
|
||||
return 0, errors.New("BUG: need directory")
|
||||
}
|
||||
st, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !st.IsDir() {
|
||||
return 0, fmt.Errorf("%q is not a directory", dir)
|
||||
}
|
||||
|
||||
statType, ok := st.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("cannot convert %+v to stat type for directory %q", st, dir)
|
||||
}
|
||||
|
||||
return statType.Uid, nil
|
||||
}
|
||||
|
||||
func getContainers(context *cli.Context) ([]fullContainerState, error) {
|
||||
runtimeConfig, ok := context.App.Metadata["runtimeConfig"].(oci.RuntimeConfig)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid runtime config")
|
||||
}
|
||||
|
||||
latestHypervisorDetails := getHypervisorDetails(&runtimeConfig.HypervisorConfig)
|
||||
|
||||
podList, err := vci.ListPod()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var s []fullContainerState
|
||||
|
||||
for _, pod := range podList {
|
||||
if len(pod.ContainersStatus) == 0 {
|
||||
// ignore empty pods
|
||||
continue
|
||||
}
|
||||
|
||||
currentHypervisorDetails := getHypervisorDetails(&pod.HypervisorConfig)
|
||||
|
||||
for _, container := range pod.ContainersStatus {
|
||||
ociState := oci.StatusToOCIState(container)
|
||||
staleAssets := getStaleAssets(currentHypervisorDetails, latestHypervisorDetails)
|
||||
|
||||
uid, err := getDirOwner(container.RootFs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
owner := fmt.Sprintf("#%v", uid)
|
||||
|
||||
s = append(s, fullContainerState{
|
||||
containerState: containerState{
|
||||
Version: ociState.Version,
|
||||
ID: ociState.ID,
|
||||
InitProcessPid: ociState.Pid,
|
||||
Status: ociState.Status,
|
||||
Bundle: ociState.Bundle,
|
||||
Rootfs: container.RootFs,
|
||||
Created: container.StartTime,
|
||||
Annotations: ociState.Annotations,
|
||||
Owner: owner,
|
||||
},
|
||||
CurrentHypervisorDetails: currentHypervisorDetails,
|
||||
LatestHypervisorDetails: latestHypervisorDetails,
|
||||
StaleAssets: staleAssets,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// getHypervisorDetails returns details of the latest version of the
|
||||
// hypervisor and the associated assets.
|
||||
func getHypervisorDetails(hypervisorConfig *vc.HypervisorConfig) hypervisorDetails {
|
||||
hypervisorPath, err := hypervisorConfig.HypervisorAssetPath()
|
||||
if err != nil {
|
||||
hypervisorPath = hypervisorConfig.HypervisorPath
|
||||
}
|
||||
|
||||
kernelPath, err := hypervisorConfig.KernelAssetPath()
|
||||
if err != nil {
|
||||
kernelPath = hypervisorConfig.KernelPath
|
||||
}
|
||||
|
||||
imagePath, err := hypervisorConfig.ImageAssetPath()
|
||||
if err != nil {
|
||||
imagePath = hypervisorConfig.ImagePath
|
||||
}
|
||||
|
||||
return hypervisorDetails{
|
||||
HypervisorAsset: asset{
|
||||
Path: hypervisorPath,
|
||||
Custom: hypervisorConfig.CustomHypervisorAsset(),
|
||||
},
|
||||
KernelAsset: asset{
|
||||
Path: kernelPath,
|
||||
Custom: hypervisorConfig.CustomKernelAsset(),
|
||||
},
|
||||
ImageAsset: asset{
|
||||
Path: imagePath,
|
||||
Custom: hypervisorConfig.CustomImageAsset(),
|
||||
},
|
||||
}
|
||||
}
|
754
cli/list_test.go
Normal file
754
cli/list_test.go
Normal file
@ -0,0 +1,754 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
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) {
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, nil, nil)
|
||||
app.Name = "foo"
|
||||
ctx.App.Metadata = map[string]interface{}{
|
||||
"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 TestListGetContainersListPodFail(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir(testDir, "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, nil, nil)
|
||||
app.Name = "foo"
|
||||
|
||||
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
|
||||
assert.NoError(err)
|
||||
|
||||
ctx.App.Metadata = map[string]interface{}{
|
||||
"runtimeConfig": runtimeConfig,
|
||||
}
|
||||
|
||||
_, err = getContainers(ctx)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestListGetContainers(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
// No pre-existing pods
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
tmpdir, err := ioutil.TempDir(testDir, "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, nil, nil)
|
||||
app.Name = "foo"
|
||||
|
||||
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
|
||||
assert.NoError(err)
|
||||
|
||||
ctx.App.Metadata = map[string]interface{}{
|
||||
"runtimeConfig": runtimeConfig,
|
||||
}
|
||||
|
||||
state, err := getContainers(ctx)
|
||||
assert.NoError(err)
|
||||
assert.Equal(state, []fullContainerState(nil))
|
||||
}
|
||||
|
||||
func TestListGetContainersPodWithoutContainers(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus(nil),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
tmpdir, err := ioutil.TempDir(testDir, "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, nil, nil)
|
||||
app.Name = "foo"
|
||||
|
||||
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
|
||||
assert.NoError(err)
|
||||
|
||||
ctx.App.Metadata = map[string]interface{}{
|
||||
"runtimeConfig": runtimeConfig,
|
||||
}
|
||||
|
||||
state, err := getContainers(ctx)
|
||||
assert.NoError(err)
|
||||
assert.Equal(state, []fullContainerState(nil))
|
||||
}
|
||||
|
||||
func TestListGetContainersPodWithContainer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir(testDir, "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
rootfs := filepath.Join(tmpdir, "rootfs")
|
||||
err = os.MkdirAll(rootfs, testDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{},
|
||||
RootFs: rootfs,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, nil, nil)
|
||||
app.Name = "foo"
|
||||
|
||||
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
|
||||
assert.NoError(err)
|
||||
|
||||
ctx.App.Metadata = map[string]interface{}{
|
||||
"runtimeConfig": runtimeConfig,
|
||||
}
|
||||
|
||||
_, err = getContainers(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},
|
||||
}
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
rootfs := filepath.Join(tmpdir, "rootfs")
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
},
|
||||
RootFs: rootfs,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = 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
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, d.flags, nil)
|
||||
app.Name = "foo"
|
||||
ctx.App.Metadata = map[string]interface{}{
|
||||
"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
|
||||
|
||||
_ = os.Remove(rootfs)
|
||||
|
||||
err = fn(ctx)
|
||||
assert.Error(err)
|
||||
|
||||
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)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
rootfs := filepath.Join(tmpdir, "rootfs")
|
||||
err = os.MkdirAll(rootfs, testDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
},
|
||||
RootFs: rootfs,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Bool("quiet", true, "")
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, set, nil)
|
||||
app.Name = "foo"
|
||||
ctx.App.Metadata = map[string]interface{}{
|
||||
"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 := getFileContents(output)
|
||||
assert.NoError(err)
|
||||
|
||||
trimmed := strings.TrimSpace(text)
|
||||
assert.Equal(testPodID, 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)
|
||||
}
|
79
cli/logger.go
Normal file
79
cli/logger.go
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"log/syslog"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||
)
|
||||
|
||||
// sysLogHook wraps a syslog logrus hook and a formatter to be used for all
|
||||
// syslog entries.
|
||||
//
|
||||
// This is necessary to allow the main logger (for "--log=") to use a custom
|
||||
// formatter ("--log-format=") whilst allowing the system logger to use a
|
||||
// different formatter.
|
||||
type sysLogHook struct {
|
||||
shook *lSyslog.SyslogHook
|
||||
formatter logrus.Formatter
|
||||
}
|
||||
|
||||
func (h *sysLogHook) Levels() []logrus.Level {
|
||||
return h.shook.Levels()
|
||||
}
|
||||
|
||||
// Fire is responsible for adding a log entry to the system log. It switches
|
||||
// formatter before adding the system log entry, then reverts the original log
|
||||
// formatter.
|
||||
func (h *sysLogHook) Fire(e *logrus.Entry) (err error) {
|
||||
formatter := e.Logger.Formatter
|
||||
|
||||
e.Logger.Formatter = h.formatter
|
||||
|
||||
err = h.shook.Fire(e)
|
||||
|
||||
e.Logger.Formatter = formatter
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func newSystemLogHook(network, raddr string) (*sysLogHook, error) {
|
||||
hook, err := lSyslog.NewSyslogHook(network, raddr, syslog.LOG_INFO, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &sysLogHook{
|
||||
formatter: &logrus.TextFormatter{
|
||||
TimestampFormat: time.RFC3339Nano,
|
||||
},
|
||||
shook: hook,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handleSystemLog sets up the system-level logger.
|
||||
func handleSystemLog(network, raddr string) error {
|
||||
hook, err := newSystemLogHook(network, raddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kataLog.Logger.Hooks.Add(hook)
|
||||
|
||||
return nil
|
||||
}
|
163
cli/logger_test.go
Normal file
163
cli/logger_test.go
Normal file
@ -0,0 +1,163 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testData struct {
|
||||
network string
|
||||
raddr string
|
||||
expectError bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Ensure all log levels are logged
|
||||
kataLog.Logger.Level = logrus.DebugLevel
|
||||
|
||||
// Discard log output
|
||||
kataLog.Logger.Out = ioutil.Discard
|
||||
}
|
||||
|
||||
func TestHandleSystemLog(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
data := []testData{
|
||||
{"invalid-net-type", "999.999.999.999", true},
|
||||
{"invalid net-type", "a a ", true},
|
||||
{"invalid-net-type", ".", true},
|
||||
{"moo", "999.999.999.999", true},
|
||||
{"moo", "999.999.999.999:99999999999999999", true},
|
||||
{"qwerty", "uiop:ftw!", true},
|
||||
{"", "", false},
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
err := handleSystemLog(d.network, d.raddr)
|
||||
if d.expectError {
|
||||
assert.Error(err, fmt.Sprintf("%+v", d))
|
||||
} else {
|
||||
assert.NoError(err, fmt.Sprintf("%+v", d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSystemLogHook(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
hook, err := newSystemLogHook("", "")
|
||||
assert.NoError(err)
|
||||
|
||||
msg := "wibble"
|
||||
level := logrus.DebugLevel
|
||||
|
||||
logger := logrus.New()
|
||||
|
||||
// ensure all output is displayed
|
||||
logger.Level = logrus.DebugLevel
|
||||
|
||||
// throw away all stdout so that the Format() call
|
||||
// below returns the data in structured form.
|
||||
logger.Out = ioutil.Discard
|
||||
|
||||
entry := &logrus.Entry{
|
||||
Logger: logger,
|
||||
|
||||
// UTC for time.Parse()
|
||||
Time: time.Now().UTC(),
|
||||
|
||||
Message: msg,
|
||||
Level: level,
|
||||
}
|
||||
|
||||
// call the formatting function directly and see if the output
|
||||
// matches what we expect.
|
||||
bytes, err := hook.formatter.Format(entry)
|
||||
assert.NoError(err)
|
||||
|
||||
output := string(bytes)
|
||||
output = strings.TrimSpace(output)
|
||||
output = strings.Replace(output, `"`, "", -1)
|
||||
|
||||
fields := strings.Fields(output)
|
||||
|
||||
msgFound := ""
|
||||
timeFound := ""
|
||||
levelFound := ""
|
||||
|
||||
// look for the expected fields
|
||||
for _, field := range fields {
|
||||
|
||||
// split each structured field into name and value fields
|
||||
f := strings.Split(field, "=")
|
||||
assert.True(len(f) >= 2)
|
||||
|
||||
switch f[0] {
|
||||
case "level":
|
||||
levelFound = f[1]
|
||||
case "msg":
|
||||
msgFound = f[1]
|
||||
case "time":
|
||||
timeFound = f[1]
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(levelFound, level.String())
|
||||
assert.Equal(msgFound, msg)
|
||||
assert.NotEqual(timeFound, "")
|
||||
|
||||
// Tell time.Parse() how to handle the timestamps by putting it into
|
||||
// the standard golang time format equivalent to:
|
||||
//
|
||||
// "Mon Jan 2 15:04:05 -0700 MST 2006".
|
||||
//
|
||||
expectedTimeFormat := "2006-01-02T15:04:05.999999999Z"
|
||||
|
||||
// Note that time.Parse() assumes a UTC time.
|
||||
_, err = time.Parse(expectedTimeFormat, timeFound)
|
||||
assert.NoError(err)
|
||||
|
||||
// time.Parse() is "clever" but also doesn't check anything more
|
||||
// granular than a second, so let's be completely paranoid and check
|
||||
// via regular expression too.
|
||||
expectedPattern :=
|
||||
// YYYY-MM-DD
|
||||
`\d{4}-\d{2}-\d{2}` +
|
||||
// time separator
|
||||
`T` +
|
||||
// HH:MM:SS
|
||||
`\d{2}:\d{2}:\d{2}` +
|
||||
// high-precision separator
|
||||
`.` +
|
||||
// nano-seconds. Note that the quantifier range is
|
||||
// required because the time.RFC3339Nano format
|
||||
// trunctates trailing zeros.
|
||||
`\d{1,9}` +
|
||||
// UTC timezone specifier
|
||||
`Z`
|
||||
|
||||
expectedRE := regexp.MustCompile(expectedPattern)
|
||||
matched := expectedRE.FindAllStringSubmatch(timeFound, -1)
|
||||
assert.NotNil(matched, "expected time in format %q, got %q", expectedPattern, timeFound)
|
||||
}
|
386
cli/main.go
Normal file
386
cli/main.go
Normal file
@ -0,0 +1,386 @@
|
||||
// Copyright (c) 2014,2015,2016 Docker, Inc.
|
||||
// Copyright (c) 2017-2018 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
goruntime "runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// specConfig is the name of the file holding the containers configuration
|
||||
const specConfig = "config.json"
|
||||
|
||||
// arch is the architecture for the running program
|
||||
const arch = goruntime.GOARCH
|
||||
|
||||
var usage = fmt.Sprintf(`%s runtime
|
||||
|
||||
%s is a command line program for running applications packaged
|
||||
according to the Open Container Initiative (OCI).`, name, name)
|
||||
|
||||
var notes = fmt.Sprintf(`
|
||||
NOTES:
|
||||
|
||||
- Commands starting "%s-" and options starting "--%s-" are `+project+` extensions.
|
||||
|
||||
URL:
|
||||
|
||||
The canonical URL for this project is: %s
|
||||
|
||||
`, projectPrefix, projectPrefix, projectURL)
|
||||
|
||||
// kataLog is the logger used to record all messages
|
||||
var kataLog *logrus.Entry
|
||||
|
||||
// originalLoggerLevel is the default log level. It is used to revert the
|
||||
// current log level back to its original value if debug output is not
|
||||
// required.
|
||||
var originalLoggerLevel logrus.Level
|
||||
|
||||
// if true, coredump when an internal error occurs or a fatal signal is received
|
||||
var crashOnError = false
|
||||
|
||||
// concrete virtcontainer implementation
|
||||
var virtcontainersImpl = &vc.VCImpl{}
|
||||
|
||||
// vci is used to access a particular virtcontainers implementation.
|
||||
// Normally, it refers to the official package, but is re-assigned in
|
||||
// the tests to allow virtcontainers to be mocked.
|
||||
var vci vc.VC = virtcontainersImpl
|
||||
|
||||
// defaultOutputFile is the default output file to write the gathered
|
||||
// information to.
|
||||
var defaultOutputFile = os.Stdout
|
||||
|
||||
// defaultErrorFile is the default output file to write error
|
||||
// messages to.
|
||||
var defaultErrorFile = os.Stderr
|
||||
|
||||
// runtimeFlags is the list of supported global command-line flags
|
||||
var runtimeFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: configFilePathOption,
|
||||
Usage: project + " config file path",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "log",
|
||||
Value: "/dev/null",
|
||||
Usage: "set the log file path where internal debug information is written",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "log-format",
|
||||
Value: "text",
|
||||
Usage: "set the format used by logs ('text' (default), or 'json')",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "root",
|
||||
Value: defaultRootDirectory,
|
||||
Usage: "root directory for storage of container state (this should be located in tmpfs)",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: showConfigPathsOption,
|
||||
Usage: "show config file paths that will be checked for (in order)",
|
||||
},
|
||||
}
|
||||
|
||||
// runtimeCommands is the list of supported command-line (sub-)
|
||||
// commands.
|
||||
var runtimeCommands = []cli.Command{
|
||||
createCLICommand,
|
||||
deleteCLICommand,
|
||||
execCLICommand,
|
||||
killCLICommand,
|
||||
listCLICommand,
|
||||
pauseCLICommand,
|
||||
psCLICommand,
|
||||
resumeCLICommand,
|
||||
runCLICommand,
|
||||
startCLICommand,
|
||||
stateCLICommand,
|
||||
versionCLICommand,
|
||||
|
||||
// Kata Containers specific extensions
|
||||
kataCheckCLICommand,
|
||||
kataEnvCLICommand,
|
||||
}
|
||||
|
||||
// runtimeBeforeSubcommands is the function to run before command-line
|
||||
// parsing occurs.
|
||||
var runtimeBeforeSubcommands = beforeSubcommands
|
||||
|
||||
// runtimeCommandNotFound is the function to handle an invalid sub-command.
|
||||
var runtimeCommandNotFound = commandNotFound
|
||||
|
||||
// runtimeVersion is the function that returns the full version
|
||||
// string describing the runtime.
|
||||
var runtimeVersion = makeVersionString
|
||||
|
||||
// saved default cli package values (for testing).
|
||||
var savedCLIAppHelpTemplate = cli.AppHelpTemplate
|
||||
var savedCLIVersionPrinter = cli.VersionPrinter
|
||||
var savedCLIErrWriter = cli.ErrWriter
|
||||
|
||||
func init() {
|
||||
kataLog = logrus.WithFields(logrus.Fields{
|
||||
"name": name,
|
||||
"source": "runtime",
|
||||
"pid": os.Getpid(),
|
||||
})
|
||||
|
||||
// Save the original log level and then set to debug level to ensure
|
||||
// that any problems detected before the config file is parsed are
|
||||
// logged. This is required since the config file determines the true
|
||||
// log level for the runtime: once parsed the log level is set
|
||||
// appropriately but for issues between now and completion of the
|
||||
// config file parsing, it is prudent to operate in verbose mode.
|
||||
originalLoggerLevel = kataLog.Logger.Level
|
||||
kataLog.Logger.Level = logrus.DebugLevel
|
||||
}
|
||||
|
||||
func setupSignalHandler() {
|
||||
sigCh := make(chan os.Signal, 8)
|
||||
|
||||
for _, sig := range fatalSignals() {
|
||||
signal.Notify(sigCh, sig)
|
||||
}
|
||||
|
||||
go func() {
|
||||
sig := <-sigCh
|
||||
|
||||
nativeSignal, ok := sig.(syscall.Signal)
|
||||
if ok {
|
||||
if fatalSignal(nativeSignal) {
|
||||
kataLog.WithField("signal", sig).Error("received fatal signal")
|
||||
die()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// beforeSubcommands is the function to perform preliminary checks
|
||||
// before command-line parsing occurs.
|
||||
func beforeSubcommands(context *cli.Context) error {
|
||||
if context.GlobalBool(showConfigPathsOption) {
|
||||
files := getDefaultConfigFilePaths()
|
||||
|
||||
for _, file := range files {
|
||||
fmt.Fprintf(defaultOutputFile, "%s\n", file)
|
||||
}
|
||||
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if userWantsUsage(context) || (context.NArg() == 1 && (context.Args()[0] == checkCmd)) {
|
||||
// No setup required if the user just
|
||||
// wants to see the usage statement or are
|
||||
// running a command that does not manipulate
|
||||
// containers.
|
||||
return nil
|
||||
}
|
||||
|
||||
if path := context.GlobalString("log"); path != "" {
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kataLog.Logger.Out = f
|
||||
}
|
||||
|
||||
switch context.GlobalString("log-format") {
|
||||
case "text":
|
||||
// retain logrus's default.
|
||||
case "json":
|
||||
kataLog.Logger.Formatter = new(logrus.JSONFormatter)
|
||||
default:
|
||||
return fmt.Errorf("unknown log-format %q", context.GlobalString("log-format"))
|
||||
}
|
||||
|
||||
// Set virtcontainers logger.
|
||||
vci.SetLogger(kataLog)
|
||||
|
||||
// Set the OCI package logger.
|
||||
oci.SetLogger(kataLog)
|
||||
|
||||
ignoreLogging := false
|
||||
|
||||
// Add the name of the sub-command to each log entry for easier
|
||||
// debugging.
|
||||
cmdName := context.Args().First()
|
||||
if context.App.Command(cmdName) != nil {
|
||||
kataLog = kataLog.WithField("command", cmdName)
|
||||
}
|
||||
|
||||
if context.NArg() == 1 && context.Args()[0] == envCmd {
|
||||
// simply report the logging setup
|
||||
ignoreLogging = true
|
||||
}
|
||||
|
||||
configFile, runtimeConfig, err := loadConfiguration(context.GlobalString(configFilePathOption), ignoreLogging)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
args := strings.Join(context.Args(), " ")
|
||||
|
||||
fields := logrus.Fields{
|
||||
"version": version,
|
||||
"commit": commit,
|
||||
"arguments": `"` + args + `"`,
|
||||
}
|
||||
|
||||
kataLog.WithFields(fields).Info()
|
||||
|
||||
// make the data accessible to the sub-commands.
|
||||
context.App.Metadata = map[string]interface{}{
|
||||
"runtimeConfig": runtimeConfig,
|
||||
"configFile": configFile,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// function called when an invalid command is specified which causes the
|
||||
// runtime to error.
|
||||
func commandNotFound(c *cli.Context, command string) {
|
||||
err := fmt.Errorf("Invalid command %q", command)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
// makeVersionString returns a multi-line string describing the runtime
|
||||
// version along with the version of the OCI specification it supports.
|
||||
func makeVersionString() string {
|
||||
v := make([]string, 0, 3)
|
||||
|
||||
versionStr := version
|
||||
if versionStr == "" {
|
||||
versionStr = unknown
|
||||
}
|
||||
|
||||
v = append(v, name+" : "+versionStr)
|
||||
|
||||
commitStr := commit
|
||||
if commitStr == "" {
|
||||
commitStr = unknown
|
||||
}
|
||||
|
||||
v = append(v, " commit : "+commitStr)
|
||||
|
||||
specVersionStr := specs.Version
|
||||
if specVersionStr == "" {
|
||||
specVersionStr = unknown
|
||||
}
|
||||
|
||||
v = append(v, " OCI specs: "+specVersionStr)
|
||||
|
||||
return strings.Join(v, "\n")
|
||||
}
|
||||
|
||||
// setCLIGlobals modifies various cli package global variables
|
||||
func setCLIGlobals() {
|
||||
cli.AppHelpTemplate = fmt.Sprintf(`%s%s`, cli.AppHelpTemplate, notes)
|
||||
|
||||
// Override the default function to display version details to
|
||||
// ensure the "--version" option and "version" command are identical.
|
||||
cli.VersionPrinter = func(c *cli.Context) {
|
||||
fmt.Fprintln(defaultOutputFile, c.App.Version)
|
||||
}
|
||||
|
||||
// If the command returns an error, cli takes upon itself to print
|
||||
// the error on cli.ErrWriter and exit.
|
||||
// Use our own writer here to ensure the log gets sent to the right
|
||||
// location.
|
||||
cli.ErrWriter = &fatalWriter{cli.ErrWriter}
|
||||
}
|
||||
|
||||
// createRuntimeApp creates an application to process the command-line
|
||||
// arguments and invoke the requested runtime command.
|
||||
func createRuntimeApp(args []string) error {
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Name = name
|
||||
app.Writer = defaultOutputFile
|
||||
app.Usage = usage
|
||||
app.CommandNotFound = runtimeCommandNotFound
|
||||
app.Version = runtimeVersion()
|
||||
app.Flags = runtimeFlags
|
||||
app.Commands = runtimeCommands
|
||||
app.Before = runtimeBeforeSubcommands
|
||||
app.EnableBashCompletion = true
|
||||
|
||||
return app.Run(args)
|
||||
}
|
||||
|
||||
// userWantsUsage determines if the user only wishes to see the usage
|
||||
// statement.
|
||||
func userWantsUsage(context *cli.Context) bool {
|
||||
if context.NArg() == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if context.NArg() == 1 && (context.Args()[0] == "help" || context.Args()[0] == "version") {
|
||||
return true
|
||||
}
|
||||
|
||||
if context.NArg() >= 2 && (context.Args()[1] == "-h" || context.Args()[1] == "--help") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// fatal prints the error's details exits the program.
|
||||
func fatal(err error) {
|
||||
kataLog.Error(err)
|
||||
fmt.Fprintln(defaultErrorFile, err)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
type fatalWriter struct {
|
||||
cliErrWriter io.Writer
|
||||
}
|
||||
|
||||
func (f *fatalWriter) Write(p []byte) (n int, err error) {
|
||||
// Ensure error is logged before displaying to the user
|
||||
kataLog.Error(string(p))
|
||||
return f.cliErrWriter.Write(p)
|
||||
}
|
||||
|
||||
func createRuntime() {
|
||||
setupSignalHandler()
|
||||
|
||||
setCLIGlobals()
|
||||
|
||||
err := createRuntimeApp(os.Args)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer handlePanic()
|
||||
createRuntime()
|
||||
}
|
1115
cli/main_test.go
Normal file
1115
cli/main_test.go
Normal file
File diff suppressed because it is too large
Load Diff
351
cli/oci.go
Normal file
351
cli/oci.go
Normal file
@ -0,0 +1,351 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
"github.com/opencontainers/runc/libcontainer/utils"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Contants related to cgroup memory directory
|
||||
const (
|
||||
cgroupsTasksFile = "tasks"
|
||||
cgroupsProcsFile = "cgroup.procs"
|
||||
cgroupsDirMode = os.FileMode(0750)
|
||||
cgroupsFileMode = os.FileMode(0640)
|
||||
|
||||
// Filesystem type corresponding to CGROUP_SUPER_MAGIC as listed
|
||||
// here: http://man7.org/linux/man-pages/man2/statfs.2.html
|
||||
cgroupFsType = 0x27e0eb
|
||||
)
|
||||
|
||||
var errNeedLinuxResource = errors.New("Linux resource cannot be empty")
|
||||
|
||||
var cgroupsDirPath string
|
||||
|
||||
var procMountInfo = "/proc/self/mountinfo"
|
||||
|
||||
// getContainerInfo returns the container status and its pod ID.
|
||||
// It internally expands the container ID from the prefix provided.
|
||||
func getContainerInfo(containerID string) (vc.ContainerStatus, string, error) {
|
||||
// container ID MUST be provided.
|
||||
if containerID == "" {
|
||||
return vc.ContainerStatus{}, "", fmt.Errorf("Missing container ID")
|
||||
}
|
||||
|
||||
podStatusList, err := vci.ListPod()
|
||||
if err != nil {
|
||||
return vc.ContainerStatus{}, "", err
|
||||
}
|
||||
|
||||
for _, podStatus := range podStatusList {
|
||||
for _, containerStatus := range podStatus.ContainersStatus {
|
||||
if containerStatus.ID == containerID {
|
||||
return containerStatus, podStatus.ID, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not finding a container should not trigger an error as
|
||||
// getContainerInfo is used for checking the existence and
|
||||
// the absence of a container ID.
|
||||
return vc.ContainerStatus{}, "", nil
|
||||
}
|
||||
|
||||
func getExistingContainerInfo(containerID string) (vc.ContainerStatus, string, error) {
|
||||
cStatus, podID, err := getContainerInfo(containerID)
|
||||
if err != nil {
|
||||
return vc.ContainerStatus{}, "", err
|
||||
}
|
||||
|
||||
// container ID MUST exist.
|
||||
if cStatus.ID == "" {
|
||||
return vc.ContainerStatus{}, "", fmt.Errorf("Container ID (%v) does not exist", containerID)
|
||||
}
|
||||
|
||||
return cStatus, podID, nil
|
||||
}
|
||||
|
||||
func validCreateParams(containerID, bundlePath string) (string, error) {
|
||||
// container ID MUST be provided.
|
||||
if containerID == "" {
|
||||
return "", fmt.Errorf("Missing container ID")
|
||||
}
|
||||
|
||||
// container ID MUST be unique.
|
||||
cStatus, _, err := getContainerInfo(containerID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if cStatus.ID != "" {
|
||||
return "", fmt.Errorf("ID already in use, unique ID should be provided")
|
||||
}
|
||||
|
||||
// bundle path MUST be provided.
|
||||
if bundlePath == "" {
|
||||
return "", fmt.Errorf("Missing bundle path")
|
||||
}
|
||||
|
||||
// bundle path MUST be valid.
|
||||
fileInfo, err := os.Stat(bundlePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Invalid bundle path '%s': %s", bundlePath, err)
|
||||
}
|
||||
if fileInfo.IsDir() == false {
|
||||
return "", fmt.Errorf("Invalid bundle path '%s', it should be a directory", bundlePath)
|
||||
}
|
||||
|
||||
resolved, err := resolvePath(bundlePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
// processCgroupsPath process the cgroups path as expected from the
|
||||
// OCI runtime specification. It returns a list of complete paths
|
||||
// that should be created and used for every specified resource.
|
||||
func processCgroupsPath(ociSpec oci.CompatOCISpec, isPod bool) ([]string, error) {
|
||||
var cgroupsPathList []string
|
||||
|
||||
if ociSpec.Linux.CgroupsPath == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
if ociSpec.Linux.Resources == nil {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
if ociSpec.Linux.Resources.Memory != nil {
|
||||
memCgroupsPath, err := processCgroupsPathForResource(ociSpec, "memory", isPod)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
if memCgroupsPath != "" {
|
||||
cgroupsPathList = append(cgroupsPathList, memCgroupsPath)
|
||||
}
|
||||
}
|
||||
|
||||
if ociSpec.Linux.Resources.CPU != nil {
|
||||
cpuCgroupsPath, err := processCgroupsPathForResource(ociSpec, "cpu", isPod)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
if cpuCgroupsPath != "" {
|
||||
cgroupsPathList = append(cgroupsPathList, cpuCgroupsPath)
|
||||
}
|
||||
}
|
||||
|
||||
if ociSpec.Linux.Resources.Pids != nil {
|
||||
pidsCgroupsPath, err := processCgroupsPathForResource(ociSpec, "pids", isPod)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
if pidsCgroupsPath != "" {
|
||||
cgroupsPathList = append(cgroupsPathList, pidsCgroupsPath)
|
||||
}
|
||||
}
|
||||
|
||||
if ociSpec.Linux.Resources.BlockIO != nil {
|
||||
blkIOCgroupsPath, err := processCgroupsPathForResource(ociSpec, "blkio", isPod)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
if blkIOCgroupsPath != "" {
|
||||
cgroupsPathList = append(cgroupsPathList, blkIOCgroupsPath)
|
||||
}
|
||||
}
|
||||
|
||||
return cgroupsPathList, nil
|
||||
}
|
||||
|
||||
func processCgroupsPathForResource(ociSpec oci.CompatOCISpec, resource string, isPod bool) (string, error) {
|
||||
if resource == "" {
|
||||
return "", errNeedLinuxResource
|
||||
}
|
||||
|
||||
var err error
|
||||
cgroupsDirPath, err = getCgroupsDirPath(procMountInfo)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get CgroupsDirPath error: %s", err)
|
||||
}
|
||||
|
||||
// Relative cgroups path provided.
|
||||
if filepath.IsAbs(ociSpec.Linux.CgroupsPath) == false {
|
||||
return filepath.Join(cgroupsDirPath, resource, ociSpec.Linux.CgroupsPath), nil
|
||||
}
|
||||
|
||||
// Absolute cgroups path provided.
|
||||
var cgroupMount specs.Mount
|
||||
cgroupMountFound := false
|
||||
for _, mount := range ociSpec.Mounts {
|
||||
if mount.Type == "cgroup" {
|
||||
cgroupMount = mount
|
||||
cgroupMountFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !cgroupMountFound {
|
||||
// According to the OCI spec, an absolute path should be
|
||||
// interpreted as relative to the system cgroup mount point
|
||||
// when there is no cgroup mount point.
|
||||
return filepath.Join(cgroupsDirPath, resource, ociSpec.Linux.CgroupsPath), nil
|
||||
}
|
||||
|
||||
if cgroupMount.Destination == "" {
|
||||
return "", fmt.Errorf("cgroupsPath is absolute, cgroup mount destination cannot be empty")
|
||||
}
|
||||
|
||||
cgroupPath := filepath.Join(cgroupMount.Destination, resource)
|
||||
|
||||
// It is not an error to have this cgroup not mounted. It is usually
|
||||
// due to an old kernel version with missing support for specific
|
||||
// cgroups.
|
||||
fields := logrus.Fields{
|
||||
"path": cgroupPath,
|
||||
"type": "cgroup",
|
||||
}
|
||||
|
||||
if !isCgroupMounted(cgroupPath) {
|
||||
kataLog.WithFields(fields).Info("path not mounted")
|
||||
return "", nil
|
||||
}
|
||||
|
||||
kataLog.WithFields(fields).Info("path mounted")
|
||||
|
||||
return filepath.Join(cgroupPath, ociSpec.Linux.CgroupsPath), nil
|
||||
}
|
||||
|
||||
func isCgroupMounted(cgroupPath string) bool {
|
||||
var statFs syscall.Statfs_t
|
||||
|
||||
if err := syscall.Statfs(cgroupPath, &statFs); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if statFs.Type != int64(cgroupFsType) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func setupConsole(consolePath, consoleSockPath string) (string, error) {
|
||||
if consolePath != "" {
|
||||
return consolePath, nil
|
||||
}
|
||||
|
||||
if consoleSockPath == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
console, err := newConsole()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer console.master.Close()
|
||||
|
||||
// Open the socket path provided by the caller
|
||||
conn, err := net.Dial("unix", consoleSockPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
uConn, ok := conn.(*net.UnixConn)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("casting to *net.UnixConn failed")
|
||||
}
|
||||
|
||||
socket, err := uConn.File()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Send the parent fd through the provided socket
|
||||
if err := utils.SendFd(socket, console.master.Name(), console.master.Fd()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return console.slavePath, nil
|
||||
}
|
||||
|
||||
func noNeedForOutput(detach bool, tty bool) bool {
|
||||
if !detach {
|
||||
return false
|
||||
}
|
||||
|
||||
if !tty {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func getCgroupsDirPath(mountInfoFile string) (string, error) {
|
||||
if cgroupsDirPath != "" {
|
||||
return cgroupsDirPath, nil
|
||||
}
|
||||
|
||||
f, err := os.Open(mountInfoFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var cgroupRootPath string
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
index := strings.Index(text, " - ")
|
||||
if index < 0 {
|
||||
continue
|
||||
}
|
||||
fields := strings.Split(text, " ")
|
||||
postSeparatorFields := strings.Fields(text[index+3:])
|
||||
numPostFields := len(postSeparatorFields)
|
||||
|
||||
if len(fields) < 5 || postSeparatorFields[0] != "cgroup" || numPostFields < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
cgroupRootPath = filepath.Dir(fields[4])
|
||||
break
|
||||
}
|
||||
|
||||
if _, err = os.Stat(cgroupRootPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cgroupRootPath, nil
|
||||
}
|
623
cli/oci_test.go
Normal file
623
cli/oci_test.go
Normal file
@ -0,0 +1,623 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
vcAnnotations "github.com/kata-containers/runtime/virtcontainers/pkg/annotations"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/vcMock"
|
||||
"github.com/opencontainers/runc/libcontainer/utils"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
consolePathTest = "console-test"
|
||||
consoleSocketPathTest = "console-socket-test"
|
||||
)
|
||||
|
||||
type cgroupTestDataType struct {
|
||||
resource string
|
||||
linuxSpec *specs.LinuxResources
|
||||
}
|
||||
|
||||
var cgroupTestData = []cgroupTestDataType{
|
||||
{
|
||||
"memory",
|
||||
&specs.LinuxResources{
|
||||
Memory: &specs.LinuxMemory{},
|
||||
},
|
||||
},
|
||||
{
|
||||
"cpu",
|
||||
&specs.LinuxResources{
|
||||
CPU: &specs.LinuxCPU{},
|
||||
},
|
||||
},
|
||||
{
|
||||
"pids",
|
||||
&specs.LinuxResources{
|
||||
Pids: &specs.LinuxPids{},
|
||||
},
|
||||
},
|
||||
{
|
||||
"blkio",
|
||||
&specs.LinuxResources{
|
||||
BlockIO: &specs.LinuxBlockIO{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestGetContainerInfoContainerIDEmptyFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
status, _, err := getContainerInfo("")
|
||||
|
||||
assert.Error(err, "This test should fail because containerID is empty")
|
||||
assert.Empty(status.ID, "Expected blank fullID, but got %v", status.ID)
|
||||
}
|
||||
|
||||
func TestGetContainerInfo(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
containerID := testContainerID
|
||||
|
||||
containerStatus := vc.ContainerStatus{
|
||||
ID: containerID,
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
},
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{containerStatus},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
status, podID, err := getContainerInfo(testContainerID)
|
||||
assert.NoError(err)
|
||||
assert.Equal(podID, pod.ID())
|
||||
assert.Equal(status, containerStatus)
|
||||
}
|
||||
|
||||
func TestGetContainerInfoMismatch(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
containerID := testContainerID + testContainerID
|
||||
|
||||
containerStatus := vc.ContainerStatus{
|
||||
ID: containerID,
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
},
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{containerStatus},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
_, podID, err := getContainerInfo(testContainerID)
|
||||
assert.NoError(err)
|
||||
assert.Equal(podID, "")
|
||||
}
|
||||
|
||||
func TestValidCreateParamsContainerIDEmptyFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
_, err := validCreateParams("", "")
|
||||
|
||||
assert.Error(err, "This test should fail because containerID is empty")
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestGetExistingContainerInfoContainerIDEmptyFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
status, _, err := getExistingContainerInfo("")
|
||||
|
||||
assert.Error(err, "This test should fail because containerID is empty")
|
||||
assert.Empty(status.ID, "Expected blank fullID, but got %v", status.ID)
|
||||
}
|
||||
|
||||
func TestValidCreateParamsContainerIDNotUnique(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
containerID := testContainerID + testContainerID
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
// 2 containers with same ID
|
||||
{
|
||||
ID: containerID,
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: containerID,
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
_, err := validCreateParams(testContainerID, "")
|
||||
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestValidCreateParamsContainerIDNotUnique2(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
containerID := testContainerID + testContainerID
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: containerID,
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
_, err := validCreateParams(testContainerID, "")
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestValidCreateParamsInvalidBundle(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
bundlePath := filepath.Join(tmpdir, "bundle")
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
_, err = validCreateParams(testContainerID, bundlePath)
|
||||
// bundle is ENOENT
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestValidCreateParamsBundleIsAFile(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
bundlePath := filepath.Join(tmpdir, "bundle")
|
||||
err = createEmptyFile(bundlePath)
|
||||
assert.NoError(err)
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
_, err = validCreateParams(testContainerID, bundlePath)
|
||||
// bundle exists as a file, not a directory
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func testProcessCgroupsPath(t *testing.T, ociSpec oci.CompatOCISpec, expected []string) {
|
||||
assert := assert.New(t)
|
||||
result, err := processCgroupsPath(ociSpec, true)
|
||||
|
||||
assert.NoError(err)
|
||||
|
||||
if reflect.DeepEqual(result, expected) == false {
|
||||
assert.FailNow("DeepEqual failed", "Result path %q should match the expected one %q", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessCgroupsPathEmptyPathSuccessful(t *testing.T) {
|
||||
ociSpec := oci.CompatOCISpec{}
|
||||
|
||||
ociSpec.Linux = &specs.Linux{
|
||||
CgroupsPath: "",
|
||||
}
|
||||
|
||||
testProcessCgroupsPath(t, ociSpec, []string{})
|
||||
}
|
||||
|
||||
func TestProcessCgroupsPathEmptyResources(t *testing.T) {
|
||||
ociSpec := oci.CompatOCISpec{}
|
||||
|
||||
ociSpec.Linux = &specs.Linux{
|
||||
CgroupsPath: "foo",
|
||||
}
|
||||
|
||||
testProcessCgroupsPath(t, ociSpec, []string{})
|
||||
}
|
||||
|
||||
func TestProcessCgroupsPathRelativePathSuccessful(t *testing.T) {
|
||||
relativeCgroupsPath := "relative/cgroups/path"
|
||||
cgroupsDirPath = "/foo/runtime/base"
|
||||
|
||||
ociSpec := oci.CompatOCISpec{}
|
||||
|
||||
ociSpec.Linux = &specs.Linux{
|
||||
CgroupsPath: relativeCgroupsPath,
|
||||
}
|
||||
|
||||
for _, d := range cgroupTestData {
|
||||
ociSpec.Linux.Resources = d.linuxSpec
|
||||
|
||||
p := filepath.Join(cgroupsDirPath, d.resource, relativeCgroupsPath)
|
||||
|
||||
testProcessCgroupsPath(t, ociSpec, []string{p})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessCgroupsPathAbsoluteNoCgroupMountSuccessful(t *testing.T) {
|
||||
absoluteCgroupsPath := "/absolute/cgroups/path"
|
||||
cgroupsDirPath = "/foo/runtime/base"
|
||||
|
||||
ociSpec := oci.CompatOCISpec{}
|
||||
|
||||
ociSpec.Linux = &specs.Linux{
|
||||
CgroupsPath: absoluteCgroupsPath,
|
||||
}
|
||||
|
||||
for _, d := range cgroupTestData {
|
||||
ociSpec.Linux.Resources = d.linuxSpec
|
||||
|
||||
p := filepath.Join(cgroupsDirPath, d.resource, absoluteCgroupsPath)
|
||||
|
||||
testProcessCgroupsPath(t, ociSpec, []string{p})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessCgroupsPathAbsoluteNoCgroupMountDestinationFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
absoluteCgroupsPath := "/absolute/cgroups/path"
|
||||
|
||||
ociSpec := oci.CompatOCISpec{}
|
||||
|
||||
ociSpec.Mounts = []specs.Mount{
|
||||
{
|
||||
Type: "cgroup",
|
||||
},
|
||||
}
|
||||
|
||||
ociSpec.Linux = &specs.Linux{
|
||||
CgroupsPath: absoluteCgroupsPath,
|
||||
}
|
||||
|
||||
for _, d := range cgroupTestData {
|
||||
ociSpec.Linux.Resources = d.linuxSpec
|
||||
for _, isPod := range []bool{true, false} {
|
||||
_, err := processCgroupsPath(ociSpec, isPod)
|
||||
assert.Error(err, "This test should fail because no cgroup mount destination provided")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessCgroupsPathAbsoluteSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip(testDisabledNeedRoot)
|
||||
}
|
||||
|
||||
memoryResource := "memory"
|
||||
absoluteCgroupsPath := "/cgroup/mount/destination"
|
||||
|
||||
cgroupMountDest, err := ioutil.TempDir("", "cgroup-memory-")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(cgroupMountDest)
|
||||
|
||||
resourceMountPath := filepath.Join(cgroupMountDest, memoryResource)
|
||||
err = os.MkdirAll(resourceMountPath, cgroupsDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
err = syscall.Mount("go-test", resourceMountPath, "cgroup", 0, memoryResource)
|
||||
assert.NoError(err)
|
||||
defer syscall.Unmount(resourceMountPath, 0)
|
||||
|
||||
ociSpec := oci.CompatOCISpec{}
|
||||
|
||||
ociSpec.Linux = &specs.Linux{
|
||||
Resources: &specs.LinuxResources{
|
||||
Memory: &specs.LinuxMemory{},
|
||||
},
|
||||
CgroupsPath: absoluteCgroupsPath,
|
||||
}
|
||||
|
||||
ociSpec.Mounts = []specs.Mount{
|
||||
{
|
||||
Type: "cgroup",
|
||||
Destination: cgroupMountDest,
|
||||
},
|
||||
}
|
||||
|
||||
testProcessCgroupsPath(t, ociSpec, []string{filepath.Join(resourceMountPath, absoluteCgroupsPath)})
|
||||
}
|
||||
|
||||
func TestSetupConsoleExistingConsolePathSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
console, err := setupConsole(consolePathTest, "")
|
||||
|
||||
assert.NoError(err)
|
||||
assert.Equal(console, consolePathTest, "Got %q, Expecting %q", console, consolePathTest)
|
||||
}
|
||||
|
||||
func TestSetupConsoleExistingConsolePathAndConsoleSocketPathSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
console, err := setupConsole(consolePathTest, consoleSocketPathTest)
|
||||
|
||||
assert.NoError(err)
|
||||
assert.Equal(console, consolePathTest, "Got %q, Expecting %q", console, consolePathTest)
|
||||
}
|
||||
|
||||
func TestSetupConsoleEmptyPathsSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
console, err := setupConsole("", "")
|
||||
assert.NoError(err)
|
||||
assert.Empty(console, "Console path should be empty, got %q instead", console)
|
||||
}
|
||||
|
||||
func TestSetupConsoleExistingConsoleSocketPath(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "test-socket")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
sockName := filepath.Join(dir, "console.sock")
|
||||
|
||||
l, err := net.Listen("unix", sockName)
|
||||
assert.NoError(err)
|
||||
|
||||
console, err := setupConsole("", sockName)
|
||||
assert.NoError(err)
|
||||
|
||||
waitCh := make(chan error)
|
||||
go func() {
|
||||
conn, err1 := l.Accept()
|
||||
if err != nil {
|
||||
waitCh <- err1
|
||||
}
|
||||
|
||||
uConn, ok := conn.(*net.UnixConn)
|
||||
if !ok {
|
||||
waitCh <- fmt.Errorf("casting to *net.UnixConn failed")
|
||||
}
|
||||
|
||||
f, err1 := uConn.File()
|
||||
if err != nil {
|
||||
waitCh <- err1
|
||||
}
|
||||
|
||||
_, err1 = utils.RecvFd(f)
|
||||
waitCh <- err1
|
||||
}()
|
||||
|
||||
assert.NotEmpty(console, "Console socket path should not be empty")
|
||||
|
||||
err = <-waitCh
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
func TestSetupConsoleNotExistingSocketPathFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
console, err := setupConsole("", "unknown-sock-path")
|
||||
assert.Error(err, "This test should fail because the console socket path does not exist")
|
||||
assert.Empty(console, "This test should fail because the console socket path does not exist")
|
||||
}
|
||||
|
||||
func testNoNeedForOutput(t *testing.T, detach bool, tty bool, expected bool) {
|
||||
assert := assert.New(t)
|
||||
result := noNeedForOutput(detach, tty)
|
||||
|
||||
assert.Equal(result, expected)
|
||||
}
|
||||
|
||||
func TestNoNeedForOutputDetachTrueTtyTrue(t *testing.T) {
|
||||
testNoNeedForOutput(t, true, true, true)
|
||||
}
|
||||
|
||||
func TestNoNeedForOutputDetachFalseTtyTrue(t *testing.T) {
|
||||
testNoNeedForOutput(t, false, true, false)
|
||||
}
|
||||
|
||||
func TestNoNeedForOutputDetachFalseTtyFalse(t *testing.T) {
|
||||
testNoNeedForOutput(t, false, false, false)
|
||||
}
|
||||
|
||||
func TestNoNeedForOutputDetachTrueTtyFalse(t *testing.T) {
|
||||
testNoNeedForOutput(t, true, false, false)
|
||||
}
|
||||
|
||||
func TestIsCgroupMounted(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
r := rand.New(rand.NewSource(time.Now().Unix()))
|
||||
randPath := fmt.Sprintf("/path/to/random/%d", r.Int63())
|
||||
|
||||
assert.False(isCgroupMounted(randPath), "%s does not exist", randPath)
|
||||
|
||||
assert.False(isCgroupMounted(os.TempDir()), "%s is not a cgroup", os.TempDir())
|
||||
|
||||
cgroupsDirPath = ""
|
||||
cgroupRootPath, err := getCgroupsDirPath(procMountInfo)
|
||||
if err != nil {
|
||||
assert.NoError(err)
|
||||
}
|
||||
memoryCgroupPath := filepath.Join(cgroupRootPath, "memory")
|
||||
if _, err := os.Stat(memoryCgroupPath); os.IsNotExist(err) {
|
||||
t.Skipf("memory cgroup does not exist: %s", memoryCgroupPath)
|
||||
}
|
||||
|
||||
assert.True(isCgroupMounted(memoryCgroupPath), "%s is a cgroup", memoryCgroupPath)
|
||||
}
|
||||
|
||||
func TestProcessCgroupsPathForResource(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
bundlePath := filepath.Join(tmpdir, "bundle")
|
||||
|
||||
err = makeOCIBundle(bundlePath)
|
||||
assert.NoError(err)
|
||||
|
||||
ociConfigFile := filepath.Join(bundlePath, specConfig)
|
||||
assert.True(fileExists(ociConfigFile))
|
||||
|
||||
spec, err := readOCIConfigFile(ociConfigFile)
|
||||
assert.NoError(err)
|
||||
|
||||
for _, isPod := range []bool{true, false} {
|
||||
_, err := processCgroupsPathForResource(spec, "", isPod)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCgroupsDirPath(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
type testData struct {
|
||||
contents string
|
||||
expectedResult string
|
||||
expectError bool
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
assert.NoError(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// make sure tested cgroupsDirPath is existed
|
||||
testedCgroupDir := filepath.Join(dir, "weirdCgroup")
|
||||
err = os.Mkdir(testedCgroupDir, testDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
weirdCgroupPath := filepath.Join(testedCgroupDir, "memory")
|
||||
|
||||
data := []testData{
|
||||
{fmt.Sprintf("num1 num2 num3 / %s num6 num7 - cgroup cgroup rw,memory", weirdCgroupPath), testedCgroupDir, false},
|
||||
// cgroup mount is not properly formated, if fields post - less than 3
|
||||
{fmt.Sprintf("num1 num2 num3 / %s num6 num7 - cgroup cgroup ", weirdCgroupPath), "", true},
|
||||
{"a a a a a a a - b c d", "", true},
|
||||
{"a \na b \na b c\na b c d", "", true},
|
||||
{"", "", true},
|
||||
}
|
||||
|
||||
file := filepath.Join(dir, "mountinfo")
|
||||
|
||||
//file does not exist, should error here
|
||||
_, err = getCgroupsDirPath(file)
|
||||
assert.Error(err)
|
||||
|
||||
for _, d := range data {
|
||||
err := ioutil.WriteFile(file, []byte(d.contents), testFileMode)
|
||||
assert.NoError(err)
|
||||
|
||||
cgroupsDirPath = ""
|
||||
path, err := getCgroupsDirPath(file)
|
||||
if d.expectError {
|
||||
assert.Error(err, fmt.Sprintf("got %q, test data: %+v", path, d))
|
||||
} else {
|
||||
assert.NoError(err, fmt.Sprintf("got %q, test data: %+v", path, d))
|
||||
}
|
||||
|
||||
assert.Equal(d.expectedResult, path)
|
||||
}
|
||||
}
|
66
cli/pause.go
Normal file
66
cli/pause.go
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2014,2015,2016 Docker, Inc.
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var noteText = `Use "` + name + ` list" to identify container statuses.`
|
||||
|
||||
var pauseCLICommand = cli.Command{
|
||||
Name: "pause",
|
||||
Usage: "suspend all processes in a container",
|
||||
ArgsUsage: `<container-id>
|
||||
|
||||
Where "<container-id>" is the container name to be paused.`,
|
||||
Description: `The pause command suspends all processes in a container.
|
||||
|
||||
` + noteText,
|
||||
Action: func(context *cli.Context) error {
|
||||
return toggleContainerPause(context.Args().First(), true)
|
||||
},
|
||||
}
|
||||
|
||||
var resumeCLICommand = cli.Command{
|
||||
Name: "resume",
|
||||
Usage: "unpause all previously paused processes in a container",
|
||||
ArgsUsage: `<container-id>
|
||||
|
||||
Where "<container-id>" is the container name to be resumed.`,
|
||||
Description: `The resume command unpauses all processes in a container.
|
||||
|
||||
` + noteText,
|
||||
Action: func(context *cli.Context) error {
|
||||
return toggleContainerPause(context.Args().First(), false)
|
||||
},
|
||||
}
|
||||
|
||||
func toggleContainerPause(containerID string, pause bool) (err error) {
|
||||
// Checks the MUST and MUST NOT from OCI runtime specification
|
||||
_, podID, err := getExistingContainerInfo(containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pause {
|
||||
_, err = vci.PausePod(podID)
|
||||
} else {
|
||||
_, err = vci.ResumePod(podID)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
154
cli/pause_test.go
Normal file
154
cli/pause_test.go
Normal file
@ -0,0 +1,154 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/vcMock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
testPausePodFuncReturnNil = func(podID string) (vc.VCPod, error) {
|
||||
return &vcMock.Pod{}, nil
|
||||
}
|
||||
|
||||
testResumePodFuncReturnNil = func(podID string) (vc.VCPod, error) {
|
||||
return &vcMock.Pod{}, nil
|
||||
}
|
||||
)
|
||||
|
||||
func TestPauseCLIFunctionSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.PausePodFunc = testPausePodFuncReturnNil
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.PausePodFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID})
|
||||
|
||||
execCLICommandFunc(assert, pauseCLICommand, set, false)
|
||||
}
|
||||
|
||||
func TestPauseCLIFunctionContainerNotExistFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testingImpl.PausePodFunc = testPausePodFuncReturnNil
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.PausePodFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID})
|
||||
|
||||
execCLICommandFunc(assert, pauseCLICommand, set, true)
|
||||
}
|
||||
|
||||
func TestPauseCLIFunctionPausePodFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID})
|
||||
|
||||
execCLICommandFunc(assert, pauseCLICommand, set, true)
|
||||
}
|
||||
|
||||
func TestResumeCLIFunctionSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.ResumePodFunc = testResumePodFuncReturnNil
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.ResumePodFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID})
|
||||
|
||||
execCLICommandFunc(assert, resumeCLICommand, set, false)
|
||||
}
|
||||
|
||||
func TestResumeCLIFunctionContainerNotExistFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testingImpl.ResumePodFunc = testResumePodFuncReturnNil
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.ResumePodFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID})
|
||||
|
||||
execCLICommandFunc(assert, resumeCLICommand, set, true)
|
||||
}
|
||||
|
||||
func TestResumeCLIFunctionPausePodFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
state := vc.State{
|
||||
State: vc.StateRunning,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
|
||||
}
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
set := flag.NewFlagSet("", 0)
|
||||
set.Parse([]string{testContainerID})
|
||||
|
||||
execCLICommandFunc(assert, resumeCLICommand, set, true)
|
||||
}
|
89
cli/ps.go
Normal file
89
cli/ps.go
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2014,2015,2016 Docker, Inc.
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var psCLICommand = cli.Command{
|
||||
Name: "ps",
|
||||
Usage: "ps displays the processes running inside a container",
|
||||
ArgsUsage: `<container-id> [ps options]`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format, f",
|
||||
Value: "table",
|
||||
Usage: `select one of: ` + formatOptions,
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if context.Args().Present() == false {
|
||||
return fmt.Errorf("Missing container ID, should at least provide one")
|
||||
}
|
||||
|
||||
var args []string
|
||||
if len(context.Args()) > 1 {
|
||||
// [1:] is to remove container_id:
|
||||
// context.Args(): [container_id ps_arg1 ps_arg2 ...]
|
||||
// args: [ps_arg1 ps_arg2 ...]
|
||||
args = context.Args()[1:]
|
||||
}
|
||||
|
||||
return ps(context.Args().First(), context.String("format"), args)
|
||||
},
|
||||
SkipArgReorder: true,
|
||||
}
|
||||
|
||||
func ps(containerID, format string, args []string) error {
|
||||
if containerID == "" {
|
||||
return fmt.Errorf("Missing container ID")
|
||||
}
|
||||
|
||||
// Checks the MUST and MUST NOT from OCI runtime specification
|
||||
status, podID, err := getExistingContainerInfo(containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containerID = status.ID
|
||||
|
||||
// container MUST be running
|
||||
if status.State.State != vc.StateRunning {
|
||||
return fmt.Errorf("Container %s is not running", containerID)
|
||||
}
|
||||
|
||||
var options vc.ProcessListOptions
|
||||
|
||||
options.Args = args
|
||||
if len(options.Args) == 0 {
|
||||
options.Args = []string{"-ef"}
|
||||
}
|
||||
|
||||
options.Format = format
|
||||
|
||||
msg, err := vci.ProcessListContainer(containerID, podID, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Print(string(msg))
|
||||
|
||||
return nil
|
||||
}
|
134
cli/ps_test.go
Normal file
134
cli/ps_test.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func TestPSCLIAction(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
flagSet := flag.NewFlagSet("flag", flag.ContinueOnError)
|
||||
flagSet.Parse([]string{"runtime"})
|
||||
|
||||
// create a new fake context
|
||||
ctx := cli.NewContext(&cli.App{Metadata: map[string]interface{}{}}, flagSet, nil)
|
||||
|
||||
// get Action function
|
||||
actionFunc, ok := psCLICommand.Action.(func(ctx *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
err := actionFunc(ctx)
|
||||
assert.Error(err, "Missing container ID")
|
||||
}
|
||||
|
||||
func TestPSFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testContainerID,
|
||||
}
|
||||
|
||||
pod.MockContainers = []*vcMock.Container{
|
||||
{
|
||||
MockID: pod.ID(),
|
||||
MockPod: pod,
|
||||
},
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
// return a podStatus with the container status
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
// inexistent container
|
||||
err := ps("xyz123abc", "json", []string{"-ef"})
|
||||
assert.Error(err)
|
||||
|
||||
// container is not running
|
||||
err = ps(pod.ID(), "json", []string{"-ef"})
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestPSSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testContainerID,
|
||||
}
|
||||
|
||||
pod.MockContainers = []*vcMock.Container{
|
||||
{
|
||||
MockID: pod.ID(),
|
||||
MockPod: pod,
|
||||
},
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
// return a podStatus with the container status
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
State: vc.State{
|
||||
State: vc.StateRunning,
|
||||
},
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
testingImpl.ProcessListContainerFunc = func(podID, containerID string, options vc.ProcessListOptions) (vc.ProcessList, error) {
|
||||
return []byte("echo,sleep,grep"), nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
testingImpl.ProcessListContainerFunc = nil
|
||||
}()
|
||||
|
||||
err := ps(pod.ID(), "json", []string{})
|
||||
assert.NoError(err)
|
||||
}
|
124
cli/run.go
Normal file
124
cli/run.go
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2014,2015,2016 Docker, Inc.
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var runCLICommand = cli.Command{
|
||||
Name: "run",
|
||||
Usage: "create and run a container",
|
||||
ArgsUsage: `<container-id>
|
||||
|
||||
<container-id> is your name for the instance of the container that you
|
||||
are starting. The name you provide for the container instance must be unique
|
||||
on your host.`,
|
||||
Description: `The run command creates an instance of a container for a bundle. The bundle
|
||||
is a directory with a specification file named "config.json" and a root
|
||||
filesystem.`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "bundle, b",
|
||||
Value: "",
|
||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "console",
|
||||
Value: "",
|
||||
Usage: "path to a pseudo terminal",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "console-socket",
|
||||
Value: "",
|
||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Value: "",
|
||||
Usage: "specify the file to write the process id to",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detach, d",
|
||||
Usage: "detach from the container's process",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
runtimeConfig, ok := context.App.Metadata["runtimeConfig"].(oci.RuntimeConfig)
|
||||
if !ok {
|
||||
return errors.New("invalid runtime config")
|
||||
}
|
||||
|
||||
return run(context.Args().First(),
|
||||
context.String("bundle"),
|
||||
context.String("console"),
|
||||
context.String("console-socket"),
|
||||
context.String("pid-file"),
|
||||
context.Bool("detach"),
|
||||
runtimeConfig)
|
||||
},
|
||||
}
|
||||
|
||||
func run(containerID, bundle, console, consoleSocket, pidFile string, detach bool,
|
||||
runtimeConfig oci.RuntimeConfig) error {
|
||||
|
||||
consolePath, err := setupConsole(console, consoleSocket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := create(containerID, bundle, consolePath, pidFile, detach, runtimeConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pod, err := start(containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if detach {
|
||||
return nil
|
||||
}
|
||||
|
||||
containers := pod.GetAllContainers()
|
||||
if len(containers) == 0 {
|
||||
return fmt.Errorf("There are no containers running in the pod: %s", pod.ID())
|
||||
}
|
||||
|
||||
p, err := os.FindProcess(containers[0].GetPid())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ps, err := p.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Process state %s: %s", ps.String(), err)
|
||||
}
|
||||
|
||||
// delete container's resources
|
||||
if err := delete(pod.ID(), true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//runtime should forward container exit code to the system
|
||||
return cli.NewExitError("", ps.Sys().(syscall.WaitStatus).ExitStatus())
|
||||
}
|
655
cli/run_test.go
Normal file
655
cli/run_test.go
Normal file
@ -0,0 +1,655 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
vcAnnotations "github.com/kata-containers/runtime/virtcontainers/pkg/annotations"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/vcMock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestRunCliAction(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
flagSet := flag.NewFlagSet("flag", flag.ContinueOnError)
|
||||
flagSet.Parse([]string{"runtime"})
|
||||
|
||||
// create a new fake context
|
||||
ctx := cli.NewContext(&cli.App{Metadata: map[string]interface{}{}}, flagSet, nil)
|
||||
|
||||
// get Action function
|
||||
actionFunc, ok := runCLICommand.Action.(func(ctx *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
err := actionFunc(ctx)
|
||||
assert.Error(err, "missing runtime configuration")
|
||||
|
||||
// temporal dir to place container files
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// create a new runtime config
|
||||
runtimeConfig, err := newTestRuntimeConfig(tmpdir, "/dev/ptmx", true)
|
||||
assert.NoError(err)
|
||||
|
||||
ctx.App.Metadata = map[string]interface{}{
|
||||
"runtimeConfig": runtimeConfig,
|
||||
}
|
||||
|
||||
err = actionFunc(ctx)
|
||||
assert.Error(err, "run without args")
|
||||
}
|
||||
|
||||
func TestRunInvalidArgs(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
MockContainers: []*vcMock.Container{
|
||||
{MockID: testContainerID},
|
||||
},
|
||||
}
|
||||
|
||||
// fake functions used to run containers
|
||||
testingImpl.CreatePodFunc = func(podConfig vc.PodConfig) (vc.VCPod, error) {
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
testingImpl.StartPodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.CreatePodFunc = nil
|
||||
testingImpl.StartPodFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
// temporal dir to place container files
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// create a new bundle
|
||||
bundlePath := filepath.Join(tmpdir, "bundle")
|
||||
|
||||
err = os.MkdirAll(bundlePath, testDirMode)
|
||||
assert.NoError(err)
|
||||
|
||||
err = makeOCIBundle(bundlePath)
|
||||
assert.NoError(err)
|
||||
|
||||
// pid file
|
||||
pidFilePath := filepath.Join(tmpdir, "pid")
|
||||
|
||||
// console file
|
||||
consolePath := "/dev/ptmx"
|
||||
|
||||
// inexistent path
|
||||
inexistentPath := "/this/path/does/not/exist"
|
||||
|
||||
runtimeConfig, err := newTestRuntimeConfig(tmpdir, consolePath, true)
|
||||
assert.NoError(err)
|
||||
|
||||
type testArgs struct {
|
||||
containerID string
|
||||
bundle string
|
||||
console string
|
||||
consoleSocket string
|
||||
pidFile string
|
||||
detach bool
|
||||
runtimeConfig oci.RuntimeConfig
|
||||
}
|
||||
|
||||
args := []testArgs{
|
||||
{"", "", "", "", "", true, oci.RuntimeConfig{}},
|
||||
{"", "", "", "", "", false, oci.RuntimeConfig{}},
|
||||
{"", "", "", "", "", true, runtimeConfig},
|
||||
{"", "", "", "", "", false, runtimeConfig},
|
||||
{"", "", "", "", pidFilePath, false, runtimeConfig},
|
||||
{"", "", "", "", inexistentPath, false, runtimeConfig},
|
||||
{"", "", "", "", pidFilePath, false, runtimeConfig},
|
||||
{"", "", "", inexistentPath, pidFilePath, false, runtimeConfig},
|
||||
{"", "", inexistentPath, inexistentPath, pidFilePath, false, runtimeConfig},
|
||||
{"", "", inexistentPath, "", pidFilePath, false, runtimeConfig},
|
||||
{"", "", consolePath, "", pidFilePath, false, runtimeConfig},
|
||||
{"", bundlePath, consolePath, "", pidFilePath, false, runtimeConfig},
|
||||
{testContainerID, inexistentPath, consolePath, "", pidFilePath, false, oci.RuntimeConfig{}},
|
||||
{testContainerID, inexistentPath, consolePath, "", inexistentPath, false, oci.RuntimeConfig{}},
|
||||
{testContainerID, bundlePath, consolePath, "", pidFilePath, false, oci.RuntimeConfig{}},
|
||||
{testContainerID, inexistentPath, consolePath, "", pidFilePath, false, runtimeConfig},
|
||||
{testContainerID, inexistentPath, consolePath, "", inexistentPath, false, runtimeConfig},
|
||||
{testContainerID, bundlePath, consolePath, "", pidFilePath, false, runtimeConfig},
|
||||
}
|
||||
|
||||
for i, a := range args {
|
||||
err := run(a.containerID, a.bundle, a.console, a.consoleSocket, a.pidFile, a.detach, a.runtimeConfig)
|
||||
assert.Errorf(err, "test %d (%+v)", i, a)
|
||||
}
|
||||
}
|
||||
|
||||
type runContainerData struct {
|
||||
pidFilePath string
|
||||
consolePath string
|
||||
bundlePath string
|
||||
configJSON string
|
||||
pod *vcMock.Pod
|
||||
runtimeConfig oci.RuntimeConfig
|
||||
process *os.Process
|
||||
tmpDir string
|
||||
}
|
||||
|
||||
func testRunContainerSetup(t *testing.T) runContainerData {
|
||||
assert := assert.New(t)
|
||||
|
||||
// create a fake container workload
|
||||
workload := []string{"/bin/sleep", "10"}
|
||||
cmd := exec.Command(workload[0], workload[1:]...)
|
||||
err := cmd.Start()
|
||||
assert.NoError(err, "unable to start fake container workload %+v: %s", workload, err)
|
||||
|
||||
// temporal dir to place container files
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
|
||||
// pid file
|
||||
pidFilePath := filepath.Join(tmpdir, "pid")
|
||||
|
||||
// console file
|
||||
consolePath := "/dev/ptmx"
|
||||
|
||||
// create a new bundle
|
||||
bundlePath := filepath.Join(tmpdir, "bundle")
|
||||
|
||||
err = makeOCIBundle(bundlePath)
|
||||
assert.NoError(err)
|
||||
|
||||
// config json path
|
||||
configPath := filepath.Join(bundlePath, specConfig)
|
||||
|
||||
// pod id and container id must be the same otherwise delete will not works
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testContainerID,
|
||||
}
|
||||
|
||||
pod.MockContainers = []*vcMock.Container{
|
||||
{
|
||||
MockID: testContainerID,
|
||||
MockPid: cmd.Process.Pid,
|
||||
MockPod: pod,
|
||||
},
|
||||
}
|
||||
|
||||
// create a new runtime config
|
||||
runtimeConfig, err := newTestRuntimeConfig(tmpdir, consolePath, true)
|
||||
assert.NoError(err)
|
||||
|
||||
configJSON, err := readOCIConfigJSON(configPath)
|
||||
assert.NoError(err)
|
||||
|
||||
return runContainerData{
|
||||
pidFilePath: pidFilePath,
|
||||
consolePath: consolePath,
|
||||
bundlePath: bundlePath,
|
||||
configJSON: configJSON,
|
||||
pod: pod,
|
||||
runtimeConfig: runtimeConfig,
|
||||
process: cmd.Process,
|
||||
tmpDir: tmpdir,
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunContainerSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
d := testRunContainerSetup(t)
|
||||
defer os.RemoveAll(d.tmpDir)
|
||||
|
||||
// this flags is used to detect if createPodFunc was called
|
||||
flagCreate := false
|
||||
|
||||
// fake functions used to run containers
|
||||
testingImpl.CreatePodFunc = func(podConfig vc.PodConfig) (vc.VCPod, error) {
|
||||
flagCreate = true
|
||||
return d.pod, nil
|
||||
}
|
||||
|
||||
testingImpl.StartPodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return d.pod, nil
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
// return an empty list on create
|
||||
if !flagCreate {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
|
||||
// return a podStatus with the container status
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: d.pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: d.pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
vcAnnotations.ConfigJSONKey: d.configJSON,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
testingImpl.StartContainerFunc = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
// now we can kill the fake container workload
|
||||
err := d.process.Kill()
|
||||
assert.NoError(err)
|
||||
|
||||
return d.pod.MockContainers[0], nil
|
||||
}
|
||||
|
||||
testingImpl.DeletePodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return d.pod, nil
|
||||
}
|
||||
|
||||
testingImpl.DeleteContainerFunc = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
return d.pod.MockContainers[0], nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.CreatePodFunc = nil
|
||||
testingImpl.StartPodFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
testingImpl.StartContainerFunc = nil
|
||||
testingImpl.DeletePodFunc = nil
|
||||
testingImpl.DeleteContainerFunc = nil
|
||||
}()
|
||||
|
||||
err := run(d.pod.ID(), d.bundlePath, d.consolePath, "", d.pidFilePath, false, d.runtimeConfig)
|
||||
|
||||
// should return ExitError with the message and exit code
|
||||
e, ok := err.(*cli.ExitError)
|
||||
assert.True(ok, "error should be a cli.ExitError: %s", err)
|
||||
assert.Empty(e.Error())
|
||||
assert.NotZero(e.ExitCode())
|
||||
}
|
||||
|
||||
func TestRunContainerDetachSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
d := testRunContainerSetup(t)
|
||||
defer os.RemoveAll(d.tmpDir)
|
||||
|
||||
// this flags is used to detect if createPodFunc was called
|
||||
flagCreate := false
|
||||
|
||||
// fake functions used to run containers
|
||||
testingImpl.CreatePodFunc = func(podConfig vc.PodConfig) (vc.VCPod, error) {
|
||||
flagCreate = true
|
||||
return d.pod, nil
|
||||
}
|
||||
|
||||
testingImpl.StartPodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return d.pod, nil
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
// return an empty list on create
|
||||
if !flagCreate {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
|
||||
// return a podStatus with the container status
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: d.pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: d.pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
vcAnnotations.ConfigJSONKey: d.configJSON,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
testingImpl.StartContainerFunc = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
// now we can kill the fake container workload
|
||||
err := d.process.Kill()
|
||||
assert.NoError(err)
|
||||
|
||||
return d.pod.MockContainers[0], nil
|
||||
}
|
||||
|
||||
testingImpl.DeletePodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return d.pod, nil
|
||||
}
|
||||
|
||||
testingImpl.DeleteContainerFunc = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
return d.pod.MockContainers[0], nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.CreatePodFunc = nil
|
||||
testingImpl.StartPodFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
testingImpl.StartContainerFunc = nil
|
||||
testingImpl.DeletePodFunc = nil
|
||||
testingImpl.DeleteContainerFunc = nil
|
||||
}()
|
||||
|
||||
err := run(d.pod.ID(), d.bundlePath, d.consolePath, "", d.pidFilePath, true, d.runtimeConfig)
|
||||
|
||||
// should not return ExitError
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
func TestRunContainerDeleteFail(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
d := testRunContainerSetup(t)
|
||||
defer os.RemoveAll(d.tmpDir)
|
||||
|
||||
// this flags is used to detect if createPodFunc was called
|
||||
flagCreate := false
|
||||
|
||||
// fake functions used to run containers
|
||||
testingImpl.CreatePodFunc = func(podConfig vc.PodConfig) (vc.VCPod, error) {
|
||||
flagCreate = true
|
||||
return d.pod, nil
|
||||
}
|
||||
|
||||
testingImpl.StartPodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return d.pod, nil
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
// return an empty list on create
|
||||
if !flagCreate {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
|
||||
// return a podStatus with the container status
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: d.pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: d.pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
vcAnnotations.ConfigJSONKey: d.configJSON,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
testingImpl.StartContainerFunc = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
// now we can kill the fake container workload
|
||||
err := d.process.Kill()
|
||||
assert.NoError(err)
|
||||
|
||||
return d.pod.MockContainers[0], nil
|
||||
}
|
||||
|
||||
testingImpl.DeletePodFunc = func(podID string) (vc.VCPod, error) {
|
||||
// return an error to provoke a failure in delete
|
||||
return nil, fmt.Errorf("DeletePodFunc")
|
||||
}
|
||||
|
||||
testingImpl.DeleteContainerFunc = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
// return an error to provoke a failure in delete
|
||||
return d.pod.MockContainers[0], fmt.Errorf("DeleteContainerFunc")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.CreatePodFunc = nil
|
||||
testingImpl.StartPodFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
testingImpl.StartContainerFunc = nil
|
||||
testingImpl.DeletePodFunc = nil
|
||||
testingImpl.DeleteContainerFunc = nil
|
||||
}()
|
||||
|
||||
err := run(d.pod.ID(), d.bundlePath, d.consolePath, "", d.pidFilePath, false, d.runtimeConfig)
|
||||
|
||||
// should not return ExitError
|
||||
err, ok := err.(*cli.ExitError)
|
||||
assert.False(ok, "error should not be a cli.ExitError: %s", err)
|
||||
}
|
||||
|
||||
func TestRunContainerWaitFail(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
d := testRunContainerSetup(t)
|
||||
defer os.RemoveAll(d.tmpDir)
|
||||
|
||||
// this flags is used to detect if createPodFunc was called
|
||||
flagCreate := false
|
||||
|
||||
// fake functions used to run containers
|
||||
testingImpl.CreatePodFunc = func(podConfig vc.PodConfig) (vc.VCPod, error) {
|
||||
flagCreate = true
|
||||
return d.pod, nil
|
||||
}
|
||||
|
||||
testingImpl.StartPodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return d.pod, nil
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
// return an empty list on create
|
||||
if !flagCreate {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
|
||||
// return a podStatus with the container status
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: d.pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: d.pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
vcAnnotations.ConfigJSONKey: d.configJSON,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
testingImpl.StartContainerFunc = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
// now we can kill the fake container workload
|
||||
err := d.process.Kill()
|
||||
assert.NoError(err)
|
||||
|
||||
// change PID to provoke a failure in Wait
|
||||
d.pod.MockContainers[0].MockPid = -1
|
||||
|
||||
return d.pod.MockContainers[0], nil
|
||||
}
|
||||
|
||||
testingImpl.DeletePodFunc = func(podID string) (vc.VCPod, error) {
|
||||
// return an error to provoke a failure in delete
|
||||
return nil, fmt.Errorf("DeletePodFunc")
|
||||
}
|
||||
|
||||
testingImpl.DeleteContainerFunc = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
// return an error to provoke a failure in delete
|
||||
return d.pod.MockContainers[0], fmt.Errorf("DeleteContainerFunc")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.CreatePodFunc = nil
|
||||
testingImpl.StartPodFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
testingImpl.StartContainerFunc = nil
|
||||
testingImpl.DeletePodFunc = nil
|
||||
testingImpl.DeleteContainerFunc = nil
|
||||
}()
|
||||
|
||||
err := run(d.pod.ID(), d.bundlePath, d.consolePath, "", d.pidFilePath, false, d.runtimeConfig)
|
||||
|
||||
// should not return ExitError
|
||||
err, ok := err.(*cli.ExitError)
|
||||
assert.False(ok, "error should not be a cli.ExitError: %s", err)
|
||||
}
|
||||
|
||||
func TestRunContainerStartFail(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
d := testRunContainerSetup(t)
|
||||
defer os.RemoveAll(d.tmpDir)
|
||||
|
||||
// now we can kill the fake container workload
|
||||
err := d.process.Kill()
|
||||
assert.NoError(err)
|
||||
|
||||
// this flags is used to detect if createPodFunc was called
|
||||
flagCreate := false
|
||||
|
||||
// fake functions used to run containers
|
||||
testingImpl.CreatePodFunc = func(podConfig vc.PodConfig) (vc.VCPod, error) {
|
||||
flagCreate = true
|
||||
return d.pod, nil
|
||||
}
|
||||
|
||||
testingImpl.StartPodFunc = func(podID string) (vc.VCPod, error) {
|
||||
// start fails
|
||||
return nil, fmt.Errorf("StartPod")
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
// return an empty list on create
|
||||
if !flagCreate {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
|
||||
// return a podStatus with the container status
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: d.pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: d.pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
vcAnnotations.ConfigJSONKey: d.configJSON,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.CreatePodFunc = nil
|
||||
testingImpl.StartPodFunc = nil
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
err = run(d.pod.ID(), d.bundlePath, d.consolePath, "", d.pidFilePath, false, d.runtimeConfig)
|
||||
|
||||
// should not return ExitError
|
||||
err, ok := err.(*cli.ExitError)
|
||||
assert.False(ok, "error should not be a cli.ExitError: %s", err)
|
||||
}
|
||||
|
||||
func TestRunContainerStartFailNoContainers(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
listCallCount := 0
|
||||
|
||||
d := testRunContainerSetup(t)
|
||||
defer os.RemoveAll(d.tmpDir)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
pod.MockContainers = []*vcMock.Container{
|
||||
{
|
||||
MockID: testContainerID,
|
||||
MockPod: pod,
|
||||
},
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
listCallCount++
|
||||
|
||||
if listCallCount == 1 {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: testContainerID,
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
testingImpl.CreatePodFunc = func(podConfig vc.PodConfig) (vc.VCPod, error) {
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
testingImpl.StartPodFunc = func(podID string) (vc.VCPod, error) {
|
||||
// force no containers
|
||||
pod.MockContainers = nil
|
||||
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
testingImpl.CreatePodFunc = nil
|
||||
testingImpl.StartPodFunc = nil
|
||||
}()
|
||||
|
||||
err := run(d.pod.ID(), d.bundlePath, d.consolePath, "", d.pidFilePath, false, d.runtimeConfig)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
75
cli/start.go
Normal file
75
cli/start.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2014,2015,2016 Docker, Inc.
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var startCLICommand = cli.Command{
|
||||
Name: "start",
|
||||
Usage: "executes the user defined process in a created container",
|
||||
ArgsUsage: `<container-id> [container-id...]
|
||||
|
||||
<container-id> is your name for the instance of the container that you
|
||||
are starting. The name you provide for the container instance must be
|
||||
unique on your host.`,
|
||||
Description: `The start command executes the user defined process in a created container .`,
|
||||
Action: func(context *cli.Context) error {
|
||||
args := context.Args()
|
||||
if args.Present() == false {
|
||||
return fmt.Errorf("Missing container ID, should at least provide one")
|
||||
}
|
||||
|
||||
for _, cID := range []string(args) {
|
||||
if _, err := start(cID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func start(containerID string) (vc.VCPod, error) {
|
||||
// Checks the MUST and MUST NOT from OCI runtime specification
|
||||
status, podID, err := getExistingContainerInfo(containerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
containerID = status.ID
|
||||
|
||||
containerType, err := oci.GetContainerType(status.Annotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if containerType.IsPod() {
|
||||
return vci.StartPod(podID)
|
||||
}
|
||||
|
||||
c, err := vci.StartContainer(podID, containerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.Pod(), nil
|
||||
}
|
254
cli/start_test.go
Normal file
254
cli/start_test.go
Normal file
@ -0,0 +1,254 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func TestStartInvalidArgs(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Missing container id
|
||||
_, err := start("")
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
|
||||
// Mock Listpod error
|
||||
_, err = start(testContainerID)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
// Container missing in ListPod
|
||||
_, err = start(testContainerID)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestStartPod(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodSandbox),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
_, err := start(pod.ID())
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.StartPodFunc = func(podID string) (vc.VCPod, error) {
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.StartPodFunc = nil
|
||||
}()
|
||||
|
||||
_, err = start(pod.ID())
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestStartMissingAnnotation(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
_, err := start(pod.ID())
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestStartContainerSucessFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
pod.MockContainers = []*vcMock.Container{
|
||||
{
|
||||
MockID: testContainerID,
|
||||
MockPod: pod,
|
||||
},
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: testContainerID,
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
_, err := start(testContainerID)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
|
||||
testingImpl.StartContainerFunc = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
return pod.MockContainers[0], nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.StartContainerFunc = nil
|
||||
}()
|
||||
|
||||
_, err = start(testContainerID)
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestStartCLIFunction(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
flagSet := &flag.FlagSet{}
|
||||
app := cli.NewApp()
|
||||
|
||||
ctx := cli.NewContext(app, flagSet, nil)
|
||||
|
||||
fn, ok := startCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
// no container id in the Metadata
|
||||
err := fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.False(vcMock.IsMockError(err))
|
||||
|
||||
flagSet = flag.NewFlagSet("container-id", flag.ContinueOnError)
|
||||
flagSet.Parse([]string{"xyz"})
|
||||
ctx = cli.NewContext(app, flagSet, nil)
|
||||
|
||||
err = fn(ctx)
|
||||
assert.Error(err)
|
||||
assert.True(vcMock.IsMockError(err))
|
||||
}
|
||||
|
||||
func TestStartCLIFunctionSuccess(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testPodID,
|
||||
}
|
||||
|
||||
pod.MockContainers = []*vcMock.Container{
|
||||
{
|
||||
MockID: testContainerID,
|
||||
MockPod: pod,
|
||||
},
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: testContainerID,
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
testingImpl.StartContainerFunc = func(podID, containerID string) (vc.VCContainer, error) {
|
||||
return pod.MockContainers[0], nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
testingImpl.StartContainerFunc = nil
|
||||
}()
|
||||
|
||||
app := cli.NewApp()
|
||||
|
||||
fn, ok := startCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
flagSet := flag.NewFlagSet("test", 0)
|
||||
flagSet.Parse([]string{testContainerID})
|
||||
ctx := cli.NewContext(app, flagSet, nil)
|
||||
assert.NotNil(ctx)
|
||||
|
||||
err := fn(ctx)
|
||||
assert.NoError(err)
|
||||
}
|
64
cli/state.go
Normal file
64
cli/state.go
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2014,2015,2016 Docker, Inc.
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var stateCLICommand = cli.Command{
|
||||
Name: "state",
|
||||
Usage: "output the state of a container",
|
||||
ArgsUsage: `<container-id>
|
||||
|
||||
<container-id> is your name for the instance of the container`,
|
||||
Description: `The state command outputs current state information for the
|
||||
instance of a container.`,
|
||||
Action: func(context *cli.Context) error {
|
||||
args := context.Args()
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("Expecting only one container ID, got %d: %v", len(args), []string(args))
|
||||
}
|
||||
|
||||
return state(args.First())
|
||||
},
|
||||
}
|
||||
|
||||
func state(containerID string) error {
|
||||
// Checks the MUST and MUST NOT from OCI runtime specification
|
||||
status, _, err := getExistingContainerInfo(containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert the status to the expected State structure
|
||||
state := oci.StatusToOCIState(status)
|
||||
|
||||
stateJSON, err := json.MarshalIndent(state, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Print stateJSON to stdout
|
||||
fmt.Fprintf(os.Stdout, "%s", stateJSON)
|
||||
|
||||
return nil
|
||||
}
|
90
cli/state_test.go
Normal file
90
cli/state_test.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func TestStateCliAction(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
actionFunc, ok := stateCLICommand.Action.(func(ctx *cli.Context) error)
|
||||
assert.True(ok)
|
||||
|
||||
flagSet := flag.NewFlagSet("flag", flag.ContinueOnError)
|
||||
|
||||
// without container id
|
||||
flagSet.Parse([]string{"runtime"})
|
||||
ctx := cli.NewContext(&cli.App{}, flagSet, nil)
|
||||
err := actionFunc(ctx)
|
||||
assert.Error(err)
|
||||
|
||||
// with container id
|
||||
flagSet.Parse([]string{"runtime", testContainerID})
|
||||
ctx = cli.NewContext(&cli.App{}, flagSet, nil)
|
||||
err = actionFunc(ctx)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestStateSuccessful(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pod := &vcMock.Pod{
|
||||
MockID: testContainerID,
|
||||
}
|
||||
|
||||
pod.MockContainers = []*vcMock.Container{
|
||||
{
|
||||
MockID: pod.ID(),
|
||||
MockPod: pod,
|
||||
},
|
||||
}
|
||||
|
||||
testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
|
||||
// return a podStatus with the container status
|
||||
return []vc.PodStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
ContainersStatus: []vc.ContainerStatus{
|
||||
{
|
||||
ID: pod.ID(),
|
||||
Annotations: map[string]string{
|
||||
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
testingImpl.ListPodFunc = nil
|
||||
}()
|
||||
|
||||
// trying with an inexistent id
|
||||
err := state("123456789")
|
||||
assert.Error(err)
|
||||
|
||||
err = state(pod.ID())
|
||||
assert.NoError(err)
|
||||
}
|
207
cli/utils.go
Normal file
207
cli/utils.go
Normal file
@ -0,0 +1,207 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const unknown = "<<unknown>>"
|
||||
|
||||
// variables to allow tests to modify the values
|
||||
var (
|
||||
procVersion = "/proc/version"
|
||||
osRelease = "/etc/os-release"
|
||||
|
||||
// Clear Linux has a different path (for stateless support)
|
||||
osReleaseClr = "/usr/lib/os-release"
|
||||
)
|
||||
|
||||
func fileExists(path string) bool {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func getFileContents(file string) (string, error) {
|
||||
bytes, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
func getKernelVersion() (string, error) {
|
||||
contents, err := getFileContents(procVersion)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fields := strings.Fields(contents)
|
||||
|
||||
if len(fields) < 3 {
|
||||
return "", fmt.Errorf("unexpected contents in %v", procVersion)
|
||||
}
|
||||
|
||||
version := fields[2]
|
||||
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// getDistroDetails returns the distributions name and version string.
|
||||
// If it is not possible to determine both values an error is
|
||||
// returned.
|
||||
func getDistroDetails() (name, version string, err error) {
|
||||
files := []string{osRelease, osReleaseClr}
|
||||
|
||||
for _, file := range files {
|
||||
contents, err := getFileContents(file)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
lines := strings.Split(contents, "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "NAME=") {
|
||||
fields := strings.Split(line, "=")
|
||||
name = strings.Trim(fields[1], `"`)
|
||||
} else if strings.HasPrefix(line, "VERSION_ID=") {
|
||||
fields := strings.Split(line, "=")
|
||||
version = strings.Trim(fields[1], `"`)
|
||||
}
|
||||
}
|
||||
|
||||
if name != "" && version != "" {
|
||||
return name, version, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("failed to find expected fields in one of %v", files)
|
||||
}
|
||||
|
||||
// getCPUDetails returns the vendor and model of the CPU.
|
||||
// If it is not possible to determine both values an error is
|
||||
// returned.
|
||||
func getCPUDetails() (vendor, model string, err error) {
|
||||
cpuinfo, err := getCPUInfo(procCPUInfo)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
lines := strings.Split(cpuinfo, "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "vendor_id") {
|
||||
fields := strings.Split(line, ":")
|
||||
if len(fields) > 1 {
|
||||
vendor = strings.TrimSpace(fields[1])
|
||||
}
|
||||
} else if strings.HasPrefix(line, "model name") {
|
||||
fields := strings.Split(line, ":")
|
||||
if len(fields) > 1 {
|
||||
model = strings.TrimSpace(fields[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if vendor != "" && model != "" {
|
||||
return vendor, model, nil
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("failed to find expected fields in file %v", procCPUInfo)
|
||||
}
|
||||
|
||||
// resolvePath returns the fully resolved and expanded value of the
|
||||
// specified path.
|
||||
func resolvePath(path string) (string, error) {
|
||||
if path == "" {
|
||||
return "", fmt.Errorf("path must be specified")
|
||||
}
|
||||
|
||||
absolute, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resolved, err := filepath.EvalSymlinks(absolute)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Make the error clearer than the default
|
||||
return "", fmt.Errorf("file %v does not exist", absolute)
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
// runCommandFull returns the commands space-trimmed standard output and
|
||||
// error on success. Note that if the command fails, the requested output will
|
||||
// still be returned, along with an error.
|
||||
func runCommandFull(args []string, includeStderr bool) (string, error) {
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
var err error
|
||||
var bytes []byte
|
||||
|
||||
if includeStderr {
|
||||
bytes, err = cmd.CombinedOutput()
|
||||
} else {
|
||||
bytes, err = cmd.Output()
|
||||
}
|
||||
|
||||
trimmed := strings.TrimSpace(string(bytes))
|
||||
|
||||
return trimmed, err
|
||||
}
|
||||
|
||||
// runCommand returns the commands space-trimmed standard output on success
|
||||
func runCommand(args []string) (string, error) {
|
||||
return runCommandFull(args, false)
|
||||
}
|
||||
|
||||
// writeFile write data into specified file
|
||||
func writeFile(filePath string, data string, fileMode os.FileMode) error {
|
||||
// Normally dir should not be empty, one case is that cgroup subsystem
|
||||
// is not mounted, we will get empty dir, and we want it fail here.
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("no such file for %s", filePath)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filePath, []byte(data), fileMode); err != nil {
|
||||
return fmt.Errorf("failed to write %v to %v: %v", data, filePath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isEmptyString return if string is empty
|
||||
func isEmptyString(b []byte) bool {
|
||||
return len(bytes.Trim(b, "\n")) == 0
|
||||
}
|
422
cli/utils_test.go
Normal file
422
cli/utils_test.go
Normal file
@ -0,0 +1,422 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFileExists(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(testDir, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
file := filepath.Join(dir, "foo")
|
||||
|
||||
assert.False(t, fileExists(file),
|
||||
fmt.Sprintf("File %q should not exist", file))
|
||||
|
||||
err = createEmptyFile(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.True(t, fileExists(file),
|
||||
fmt.Sprintf("File %q should exist", file))
|
||||
}
|
||||
|
||||
func TestGetFileContents(t *testing.T) {
|
||||
type testData struct {
|
||||
contents string
|
||||
}
|
||||
|
||||
data := []testData{
|
||||
{""},
|
||||
{" "},
|
||||
{"\n"},
|
||||
{"\n\n"},
|
||||
{"\n\n\n"},
|
||||
{"foo"},
|
||||
{"foo\nbar"},
|
||||
{"processor : 0\nvendor_id : GenuineIntel\n"},
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir(testDir, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
file := filepath.Join(dir, "foo")
|
||||
|
||||
// file doesn't exist
|
||||
_, err = getFileContents(file)
|
||||
assert.Error(t, err)
|
||||
|
||||
for _, d := range data {
|
||||
// create the file
|
||||
err = ioutil.WriteFile(file, []byte(d.contents), testFileMode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(file)
|
||||
|
||||
contents, err := getFileContents(file)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, contents, d.contents)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetKernelVersion(t *testing.T) {
|
||||
type testData struct {
|
||||
contents string
|
||||
expectedVersion string
|
||||
expectError bool
|
||||
}
|
||||
|
||||
const validVersion = "1.2.3-4.5.x86_64"
|
||||
validContents := fmt.Sprintf("Linux version %s blah blah blah ...", validVersion)
|
||||
|
||||
data := []testData{
|
||||
{"", "", true},
|
||||
{"invalid contents", "", true},
|
||||
{"a b c", "c", false},
|
||||
{validContents, validVersion, false},
|
||||
}
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
subDir := filepath.Join(tmpdir, "subdir")
|
||||
err = os.MkdirAll(subDir, testDirMode)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getKernelVersion()
|
||||
assert.Error(t, err)
|
||||
|
||||
file := filepath.Join(tmpdir, "proc-version")
|
||||
|
||||
// override
|
||||
procVersion = file
|
||||
|
||||
_, err = getKernelVersion()
|
||||
// ENOENT
|
||||
assert.Error(t, err)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
|
||||
for _, d := range data {
|
||||
err := createFile(file, d.contents)
|
||||
assert.NoError(t, err)
|
||||
|
||||
version, err := getKernelVersion()
|
||||
if d.expectError {
|
||||
assert.Error(t, err, fmt.Sprintf("%+v", d))
|
||||
continue
|
||||
} else {
|
||||
assert.NoError(t, err, fmt.Sprintf("%+v", d))
|
||||
assert.Equal(t, d.expectedVersion, version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDistroDetails(t *testing.T) {
|
||||
type testData struct {
|
||||
clrContents string
|
||||
nonClrContents string
|
||||
expectedName string
|
||||
expectedVersion string
|
||||
expectError bool
|
||||
}
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
testOSRelease := filepath.Join(tmpdir, "os-release")
|
||||
testOSReleaseClr := filepath.Join(tmpdir, "os-release-clr")
|
||||
|
||||
const clrExpectedName = "clr"
|
||||
const clrExpectedVersion = "1.2.3-4"
|
||||
clrContents := fmt.Sprintf(`
|
||||
HELLO=world
|
||||
NAME="%s"
|
||||
FOO=bar
|
||||
VERSION_ID="%s"
|
||||
`, clrExpectedName, clrExpectedVersion)
|
||||
|
||||
const nonClrExpectedName = "not-clr"
|
||||
const nonClrExpectedVersion = "999"
|
||||
nonClrContents := fmt.Sprintf(`
|
||||
HELLO=world
|
||||
NAME="%s"
|
||||
FOO=bar
|
||||
VERSION_ID="%s"
|
||||
`, nonClrExpectedName, nonClrExpectedVersion)
|
||||
|
||||
subDir := filepath.Join(tmpdir, "subdir")
|
||||
err = os.MkdirAll(subDir, testDirMode)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// override
|
||||
osRelease = subDir
|
||||
|
||||
_, _, err = getDistroDetails()
|
||||
assert.Error(t, err)
|
||||
|
||||
// override
|
||||
osRelease = testOSRelease
|
||||
osReleaseClr = testOSReleaseClr
|
||||
|
||||
_, _, err = getDistroDetails()
|
||||
// ENOENT
|
||||
assert.Error(t, err)
|
||||
|
||||
data := []testData{
|
||||
{"", "", "", "", true},
|
||||
{"invalid", "", "", "", true},
|
||||
{clrContents, "", clrExpectedName, clrExpectedVersion, false},
|
||||
{"", nonClrContents, nonClrExpectedName, nonClrExpectedVersion, false},
|
||||
{clrContents, nonClrContents, nonClrExpectedName, nonClrExpectedVersion, false},
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
err := createFile(osRelease, d.nonClrContents)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = createFile(osReleaseClr, d.clrContents)
|
||||
assert.NoError(t, err)
|
||||
|
||||
name, version, err := getDistroDetails()
|
||||
if d.expectError {
|
||||
assert.Error(t, err, fmt.Sprintf("%+v", d))
|
||||
continue
|
||||
} else {
|
||||
assert.NoError(t, err, fmt.Sprintf("%+v", d))
|
||||
assert.Equal(t, d.expectedName, name)
|
||||
assert.Equal(t, d.expectedVersion, version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCPUDetails(t *testing.T) {
|
||||
type testData struct {
|
||||
contents string
|
||||
expectedVendor string
|
||||
expectedModel string
|
||||
expectError bool
|
||||
}
|
||||
|
||||
const validVendorName = "a vendor"
|
||||
validVendor := fmt.Sprintf(`vendor_id : %s`, validVendorName)
|
||||
|
||||
const validModelName = "some CPU model"
|
||||
validModel := fmt.Sprintf(`model name : %s`, validModelName)
|
||||
|
||||
validContents := fmt.Sprintf(`
|
||||
a : b
|
||||
%s
|
||||
foo : bar
|
||||
%s
|
||||
`, validVendor, validModel)
|
||||
|
||||
data := []testData{
|
||||
{"", "", "", true},
|
||||
{"invalid", "", "", true},
|
||||
{"vendor_id", "", "", true},
|
||||
{validVendor, "", "", true},
|
||||
{validModel, "", "", true},
|
||||
{validContents, validVendorName, validModelName, false},
|
||||
}
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
testProcCPUInfo := filepath.Join(tmpdir, "cpuinfo")
|
||||
|
||||
// override
|
||||
procCPUInfo = testProcCPUInfo
|
||||
|
||||
_, _, err = getCPUDetails()
|
||||
// ENOENT
|
||||
assert.Error(t, err)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
|
||||
for _, d := range data {
|
||||
err := createFile(procCPUInfo, d.contents)
|
||||
assert.NoError(t, err)
|
||||
|
||||
vendor, model, err := getCPUDetails()
|
||||
|
||||
if d.expectError {
|
||||
assert.Error(t, err, fmt.Sprintf("%+v", d))
|
||||
continue
|
||||
} else {
|
||||
assert.NoError(t, err, fmt.Sprintf("%+v", d))
|
||||
assert.Equal(t, d.expectedVendor, vendor)
|
||||
assert.Equal(t, d.expectedModel, model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUtilsResolvePathEmptyPath(t *testing.T) {
|
||||
_, err := resolvePath("")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestUtilsResolvePathValidPath(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
target := path.Join(dir, "target")
|
||||
linkDir := path.Join(dir, "a/b/c")
|
||||
linkFile := path.Join(linkDir, "link")
|
||||
|
||||
err = createEmptyFile(target)
|
||||
assert.NoError(t, err)
|
||||
|
||||
absolute, err := filepath.Abs(target)
|
||||
assert.NoError(t, err)
|
||||
|
||||
resolvedTarget, err := filepath.EvalSymlinks(absolute)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.MkdirAll(linkDir, testDirMode)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = syscall.Symlink(target, linkFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
resolvedLink, err := resolvePath(linkFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, resolvedTarget, resolvedLink)
|
||||
}
|
||||
|
||||
func TestUtilsResolvePathENOENT(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
target := path.Join(dir, "target")
|
||||
linkDir := path.Join(dir, "a/b/c")
|
||||
linkFile := path.Join(linkDir, "link")
|
||||
|
||||
err = createEmptyFile(target)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.MkdirAll(linkDir, testDirMode)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = syscall.Symlink(target, linkFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
assert.NoError(t, err)
|
||||
defer os.Chdir(cwd)
|
||||
|
||||
err = os.Chdir(dir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.RemoveAll(dir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = resolvePath(filepath.Base(linkFile))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestUtilsRunCommand(t *testing.T) {
|
||||
output, err := runCommand([]string{"true"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", output)
|
||||
}
|
||||
|
||||
func TestUtilsRunCommandCaptureStdout(t *testing.T) {
|
||||
output, err := runCommand([]string{"echo", "hello"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "hello", output)
|
||||
}
|
||||
|
||||
func TestUtilsRunCommandIgnoreStderr(t *testing.T) {
|
||||
args := []string{"/bin/sh", "-c", "echo foo >&2;exit 0"}
|
||||
|
||||
output, err := runCommand(args)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", output)
|
||||
}
|
||||
|
||||
func TestUtilsRunCommandInvalidCmds(t *testing.T) {
|
||||
invalidCommands := [][]string{
|
||||
{""},
|
||||
{"", ""},
|
||||
{" "},
|
||||
{" ", " "},
|
||||
{" ", ""},
|
||||
{"\\"},
|
||||
{"/"},
|
||||
{"/.."},
|
||||
{"../"},
|
||||
{"/tmp"},
|
||||
{"\t"},
|
||||
{"\n"},
|
||||
{"false"},
|
||||
}
|
||||
|
||||
for _, args := range invalidCommands {
|
||||
output, err := runCommand(args)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteFileErrWriteFail(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
err := writeFile("", "", 0000)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestWriteFileErrNoPath(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir(testDir, "")
|
||||
assert.NoError(err)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// attempt to write a file over an existing directory
|
||||
err = writeFile(dir, "", 0000)
|
||||
assert.Error(err)
|
||||
}
|
28
cli/version.go
Normal file
28
cli/version.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var versionCLICommand = cli.Command{
|
||||
Name: "version",
|
||||
Usage: "display version details",
|
||||
Action: func(context *cli.Context) error {
|
||||
cli.VersionPrinter(context)
|
||||
return nil
|
||||
},
|
||||
}
|
65
cli/version_test.go
Normal file
65
cli/version_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
//
|
||||
// Copyright (c) 2017 Intel Corporation
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
const testAppName = "foo"
|
||||
const testAppVersion = "0.1.0"
|
||||
|
||||
resetCLIGlobals()
|
||||
|
||||
savedRuntimeVersionFunc := runtimeVersion
|
||||
|
||||
defer func() {
|
||||
runtimeVersion = savedRuntimeVersionFunc
|
||||
}()
|
||||
|
||||
runtimeVersion := func() string {
|
||||
return testAppVersion
|
||||
}
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, nil, nil)
|
||||
app.Name = testAppName
|
||||
app.Version = runtimeVersion()
|
||||
|
||||
fn, ok := versionCLICommand.Action.(func(context *cli.Context) error)
|
||||
assert.True(t, ok)
|
||||
|
||||
tmpfile, err := ioutil.TempFile("", "")
|
||||
assert.NoError(t, err)
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
ctx.App.Writer = tmpfile
|
||||
|
||||
err = fn(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pattern := fmt.Sprintf("%s.*version.*%s", testAppName, testAppVersion)
|
||||
err = grep(pattern, tmpfile.Name())
|
||||
assert.NoError(t, err)
|
||||
}
|
Loading…
Reference in New Issue
Block a user