runtime: refactor commandline code directory

Move all command line code to `cmd` and move containerd-shim-v2 to pkg.

Fixes: #2627
Signed-off-by: Peng Tao <bergwolf@hyper.sh>
This commit is contained in:
Peng Tao
2021-09-14 12:09:08 +08:00
parent 7bf96d2457
commit 4f7cc18622
77 changed files with 32 additions and 33 deletions

View File

@@ -0,0 +1,40 @@
//
// Copyright (c) 2018-2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
// 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
// name is the name of the runtime
const name = "@RUNTIME_NAME@"
// name of the project
const project = "@PROJECT_NAME@"
// prefix used to denote non-standard CLI commands and options.
const projectPrefix = "@PROJECT_TYPE@"
// original URL for this project
const projectURL = "@PROJECT_URL@"
// Project URL's organisation name
const projectORG = "@PROJECT_ORG@"
const defaultRootDirectory = "@PKGRUNDIR@"
// commit is the git commit the runtime is compiled from.
var commit = "@COMMIT@"
// version is the runtime version.
var version = "@VERSION@"
// Default config file used by stateless systems.
var defaultRuntimeConfiguration = "@CONFIG_PATH@"
// Alternate config file that takes precedence over
// defaultRuntimeConfiguration.
var defaultSysConfRuntimeConfiguration = "@SYSCONFIG@"

View File

@@ -0,0 +1,31 @@
// Copyright (c) 2018 HyperHQ Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"os"
shimapi "github.com/containerd/containerd/runtime/v2/shim"
shim "github.com/kata-containers/kata-containers/src/runtime/pkg/containerd-shim-v2"
"github.com/kata-containers/kata-containers/src/runtime/pkg/types"
)
func shimConfig(config *shimapi.Config) {
config.NoReaper = true
config.NoSubreaper = true
}
func main() {
if len(os.Args) == 2 && os.Args[1] == "--version" {
fmt.Printf("%s containerd shim: id: %q, version: %s, commit: %v\n", project, types.DefaultKataRuntimeName, version, commit)
os.Exit(0)
}
shimapi.Run(types.DefaultKataRuntimeName, shim.New, shimConfig)
}

View File

@@ -0,0 +1,139 @@
// Copyright (c) 2020 Ant Financial
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"flag"
"net/http"
"os"
goruntime "runtime"
"text/template"
"time"
kataMonitor "github.com/kata-containers/kata-containers/src/runtime/pkg/kata-monitor"
"github.com/sirupsen/logrus"
)
var monitorListenAddr = flag.String("listen-address", ":8090", "The address to listen on for HTTP requests.")
var runtimeEndpoint = flag.String("runtime-endpoint", "/run/containerd/containerd.sock", `Endpoint of CRI container runtime service. (default: "/run/containerd/containerd.sock")`)
var logLevel = flag.String("log-level", "info", "Log level of logrus(trace/debug/info/warn/error/fatal/panic).")
// These values are overridden via ldflags
var (
appName = "kata-monitor"
// version is the kata monitor version.
version = "0.1.0"
GitCommit = "unknown-commit"
)
type versionInfo struct {
AppName string
Version string
GitCommit string
GoVersion string
Os string
Arch string
}
var versionTemplate = `{{.AppName}}
Version: {{.Version}}
Go version: {{.GoVersion}}
Git commit: {{.GitCommit}}
OS/Arch: {{.Os}}/{{.Arch}}
`
func printVersion(ver versionInfo) {
t, _ := template.New("version").Parse(versionTemplate)
if err := t.Execute(os.Stdout, ver); err != nil {
panic(err)
}
}
func main() {
ver := versionInfo{
AppName: appName,
Version: version,
GoVersion: goruntime.Version(),
Os: goruntime.GOOS,
Arch: goruntime.GOARCH,
GitCommit: GitCommit,
}
if len(os.Args) == 2 && (os.Args[1] == "--version" || os.Args[1] == "version") {
printVersion(ver)
return
}
flag.Parse()
// init logrus
initLog()
announceFields := logrus.Fields{
// properties from version info
"app": ver.AppName,
"version": ver.Version,
"go-version": ver.GoVersion,
"os": ver.Os,
"arch": ver.Arch,
"git-commit": ver.GitCommit,
// properties from command-line options
"listen-address": *monitorListenAddr,
"runtime-endpoint": *runtimeEndpoint,
"log-level": *logLevel,
}
logrus.WithFields(announceFields).Info("announce")
// create new kataMonitor
km, err := kataMonitor.NewKataMonitor(*runtimeEndpoint)
if err != nil {
panic(err)
}
// setup handlers, now only metrics is supported
m := http.NewServeMux()
m.Handle("/metrics", http.HandlerFunc(km.ProcessMetricsRequest))
m.Handle("/sandboxes", http.HandlerFunc(km.ListSandboxes))
m.Handle("/agent-url", http.HandlerFunc(km.GetAgentURL))
// for debug shim process
m.Handle("/debug/vars", http.HandlerFunc(km.ExpvarHandler))
m.Handle("/debug/pprof/", http.HandlerFunc(km.PprofIndex))
m.Handle("/debug/pprof/cmdline", http.HandlerFunc(km.PprofCmdline))
m.Handle("/debug/pprof/profile", http.HandlerFunc(km.PprofProfile))
m.Handle("/debug/pprof/symbol", http.HandlerFunc(km.PprofSymbol))
m.Handle("/debug/pprof/trace", http.HandlerFunc(km.PprofTrace))
// listening on the server
svr := &http.Server{
Handler: m,
Addr: *monitorListenAddr,
}
logrus.Fatal(svr.ListenAndServe())
}
// initLog setup logger
func initLog() {
kataMonitorLog := logrus.WithFields(logrus.Fields{
"name": "kata-monitor",
"pid": os.Getpid(),
})
// set log level, default to warn
level, err := logrus.ParseLevel(*logLevel)
if err != nil {
level = logrus.WarnLevel
}
kataMonitorLog.Logger.SetLevel(level)
kataMonitorLog.Logger.Formatter = &logrus.TextFormatter{TimestampFormat: time.RFC3339Nano}
kataMonitor.SetLogger(kataMonitorLog)
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2017 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
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)
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) 2017 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
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)
}

View File

@@ -0,0 +1,326 @@
// Copyright (c) 2018 HyperHQ Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"context"
"fmt"
"net"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
"github.com/gogo/protobuf/types"
pb "github.com/kata-containers/kata-containers/src/runtime/protocols/cache"
vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers"
vf "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/factory"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/oci"
"github.com/pkg/errors"
"github.com/urfave/cli"
"golang.org/x/sys/unix"
"google.golang.org/grpc"
)
var factorySubCmds = []cli.Command{
initFactoryCommand,
destroyFactoryCommand,
statusFactoryCommand,
}
var factoryCLICommand = cli.Command{
Name: "factory",
Usage: "manage vm factory",
Subcommands: factorySubCmds,
Action: func(context *cli.Context) {
cli.ShowSubcommandHelp(context)
},
}
type cacheServer struct {
rpc *grpc.Server
factory vc.Factory
done chan struct{}
}
var jsonVMConfig *pb.GrpcVMConfig
// Config requests base factory config and convert it to gRPC protocol.
func (s *cacheServer) Config(ctx context.Context, empty *types.Empty) (*pb.GrpcVMConfig, error) {
if jsonVMConfig == nil {
config := s.factory.Config()
var err error
jsonVMConfig, err = config.ToGrpc()
if err != nil {
return nil, err
}
}
return jsonVMConfig, nil
}
// GetBaseVM requests a paused VM and convert it to gRPC protocol.
func (s *cacheServer) GetBaseVM(ctx context.Context, empty *types.Empty) (*pb.GrpcVM, error) {
config := s.factory.Config()
vm, err := s.factory.GetBaseVM(ctx, config)
if err != nil {
return nil, errors.Wrapf(err, "failed to GetBaseVM")
}
return vm.ToGrpc(ctx, config)
}
func (s *cacheServer) quit() {
s.rpc.GracefulStop()
close(s.done)
}
// Quit will stop VMCache server after 1 second.
func (s *cacheServer) Quit(ctx context.Context, empty *types.Empty) (*types.Empty, error) {
go func() {
kataLog.Info("VM cache server will stop after 1 second")
time.Sleep(time.Second)
s.quit()
}()
return &types.Empty{}, nil
}
func (s *cacheServer) Status(ctx context.Context, empty *types.Empty) (*pb.GrpcStatus, error) {
stat := pb.GrpcStatus{
Pid: int64(os.Getpid()),
Vmstatus: s.factory.GetVMStatus(),
}
return &stat, nil
}
func getUnixListener(path string) (net.Listener, error) {
err := os.MkdirAll(filepath.Dir(path), 0755)
if err != nil {
return nil, err
}
_, err = os.Stat(path)
if err == nil {
return nil, fmt.Errorf("%s already exist. Please stop running VMCache server and remove %s", path, path)
} else if !os.IsNotExist(err) {
return nil, err
}
l, err := net.Listen("unix", path)
if err != nil {
return nil, err
}
if err = os.Chmod(path, 0600); err != nil {
l.Close()
return nil, err
}
return l, nil
}
var handledSignals = []os.Signal{
syscall.SIGTERM,
syscall.SIGINT,
syscall.SIGPIPE,
}
func handleSignals(s *cacheServer, signals chan os.Signal) {
s.done = make(chan struct{}, 1)
go func() {
for {
sig := <-signals
kataLog.WithField("signal", sig).Debug("received signal")
switch sig {
case unix.SIGPIPE:
continue
default:
s.quit()
return
}
}
}()
}
var initFactoryCommand = cli.Command{
Name: "init",
Usage: "initialize a VM factory based on kata-runtime configuration",
Action: func(c *cli.Context) error {
ctx, err := cliContextToContext(c)
if err != nil {
return err
}
runtimeConfig, ok := c.App.Metadata["runtimeConfig"].(oci.RuntimeConfig)
if !ok {
return errors.New("invalid runtime config")
}
factoryConfig := vf.Config{
Template: runtimeConfig.FactoryConfig.Template,
TemplatePath: runtimeConfig.FactoryConfig.TemplatePath,
Cache: runtimeConfig.FactoryConfig.VMCacheNumber,
VMCache: runtimeConfig.FactoryConfig.VMCacheNumber > 0,
VMConfig: vc.VMConfig{
HypervisorType: runtimeConfig.HypervisorType,
HypervisorConfig: runtimeConfig.HypervisorConfig,
AgentConfig: runtimeConfig.AgentConfig,
},
}
if runtimeConfig.FactoryConfig.VMCacheNumber > 0 {
f, err := vf.NewFactory(ctx, factoryConfig, false)
if err != nil {
return err
}
defer f.CloseFactory(ctx)
s := &cacheServer{
rpc: grpc.NewServer(),
factory: f,
}
pb.RegisterCacheServiceServer(s.rpc, s)
l, err := getUnixListener(runtimeConfig.FactoryConfig.VMCacheEndpoint)
if err != nil {
return err
}
defer l.Close()
signals := make(chan os.Signal, 8)
handleSignals(s, signals)
signal.Notify(signals, handledSignals...)
kataLog.WithField("endpoint", runtimeConfig.FactoryConfig.VMCacheEndpoint).Info("VM cache server start")
s.rpc.Serve(l)
<-s.done
kataLog.WithField("endpoint", runtimeConfig.FactoryConfig.VMCacheEndpoint).Info("VM cache server stop")
return nil
}
if runtimeConfig.FactoryConfig.Template {
kataLog.WithField("factory", factoryConfig).Info("create vm factory")
_, err := vf.NewFactory(ctx, factoryConfig, false)
if err != nil {
kataLog.WithError(err).Error("create vm factory failed")
return err
}
fmt.Fprintln(defaultOutputFile, "vm factory initialized")
} else {
const errstring = "vm factory or VMCache is not enabled"
kataLog.Error(errstring)
fmt.Fprintln(defaultOutputFile, errstring)
}
return nil
},
}
var destroyFactoryCommand = cli.Command{
Name: "destroy",
Usage: "destroy the VM factory",
Action: func(c *cli.Context) error {
ctx, err := cliContextToContext(c)
if err != nil {
return err
}
runtimeConfig, ok := c.App.Metadata["runtimeConfig"].(oci.RuntimeConfig)
if !ok {
return errors.New("invalid runtime config")
}
if runtimeConfig.FactoryConfig.VMCacheNumber > 0 {
conn, err := grpc.Dial(fmt.Sprintf("unix://%s", runtimeConfig.FactoryConfig.VMCacheEndpoint), grpc.WithInsecure())
if err != nil {
return errors.Wrapf(err, "failed to connect %q", runtimeConfig.FactoryConfig.VMCacheEndpoint)
}
defer conn.Close()
_, err = pb.NewCacheServiceClient(conn).Quit(ctx, &types.Empty{})
if err != nil {
return errors.Wrapf(err, "failed to call gRPC Quit")
}
// Wait VMCache server stop
time.Sleep(time.Second)
} else if runtimeConfig.FactoryConfig.Template {
factoryConfig := vf.Config{
Template: true,
TemplatePath: runtimeConfig.FactoryConfig.TemplatePath,
VMConfig: vc.VMConfig{
HypervisorType: runtimeConfig.HypervisorType,
HypervisorConfig: runtimeConfig.HypervisorConfig,
AgentConfig: runtimeConfig.AgentConfig,
},
}
kataLog.WithField("factory", factoryConfig).Info("load vm factory")
f, err := vf.NewFactory(ctx, factoryConfig, true)
if err != nil {
kataLog.WithError(err).Error("load vm factory failed")
// ignore error
} else {
f.CloseFactory(ctx)
}
}
fmt.Fprintln(defaultOutputFile, "vm factory destroyed")
return nil
},
}
var statusFactoryCommand = cli.Command{
Name: "status",
Usage: "query the status of VM factory",
Action: func(c *cli.Context) error {
ctx, err := cliContextToContext(c)
if err != nil {
return err
}
runtimeConfig, ok := c.App.Metadata["runtimeConfig"].(oci.RuntimeConfig)
if !ok {
return errors.New("invalid runtime config")
}
if runtimeConfig.FactoryConfig.VMCacheNumber > 0 {
conn, err := grpc.Dial(fmt.Sprintf("unix://%s", runtimeConfig.FactoryConfig.VMCacheEndpoint), grpc.WithInsecure())
if err != nil {
fmt.Fprintln(defaultOutputFile, errors.Wrapf(err, "failed to connect %q", runtimeConfig.FactoryConfig.VMCacheEndpoint))
} else {
defer conn.Close()
status, err := pb.NewCacheServiceClient(conn).Status(ctx, &types.Empty{})
if err != nil {
fmt.Fprintln(defaultOutputFile, errors.Wrapf(err, "failed to call gRPC Status\n"))
} else {
fmt.Fprintf(defaultOutputFile, "VM cache server pid = %d\n", status.Pid)
for _, vs := range status.Vmstatus {
fmt.Fprintf(defaultOutputFile, "VM pid = %d Cpu = %d Memory = %dMiB\n", vs.Pid, vs.Cpu, vs.Memory)
}
}
}
}
if runtimeConfig.FactoryConfig.Template {
factoryConfig := vf.Config{
Template: true,
TemplatePath: runtimeConfig.FactoryConfig.TemplatePath,
VMConfig: vc.VMConfig{
HypervisorType: runtimeConfig.HypervisorType,
HypervisorConfig: runtimeConfig.HypervisorConfig,
AgentConfig: runtimeConfig.AgentConfig,
},
}
kataLog.WithField("factory", factoryConfig).Info("load vm factory")
_, err := vf.NewFactory(ctx, factoryConfig, true)
if err != nil {
fmt.Fprintln(defaultOutputFile, "vm factory is off")
} else {
fmt.Fprintln(defaultOutputFile, "vm factory is on")
}
} else {
fmt.Fprintln(defaultOutputFile, "vm factory not enabled")
}
return nil
},
}

View File

@@ -0,0 +1,158 @@
// Copyright (c) 2018 HyperHQ Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"context"
"flag"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
ktu "github.com/kata-containers/kata-containers/src/runtime/pkg/katatestutils"
vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers"
)
const testDisabledAsNonRoot = "Test disabled as requires root privileges"
func TestFactoryCLIFunctionNoRuntimeConfig(t *testing.T) {
assert := assert.New(t)
ctx := createCLIContext(nil)
ctx.App.Name = "foo"
ctx.App.Metadata["foo"] = "bar"
fn, ok := initFactoryCommand.Action.(func(context *cli.Context) error)
assert.True(ok)
err := fn(ctx)
// no runtime config in the Metadata
assert.Error(err)
fn, ok = destroyFactoryCommand.Action.(func(context *cli.Context) error)
assert.True(ok)
err = fn(ctx)
// no runtime config in the Metadata
assert.Error(err)
}
func TestFactoryCLIFunctionInit(t *testing.T) {
assert := assert.New(t)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
set := flag.NewFlagSet("", 0)
set.String("console-socket", "", "")
ctx := createCLIContext(set)
ctx.App.Name = "foo"
// No template
ctx.App.Metadata["runtimeConfig"] = runtimeConfig
fn, ok := initFactoryCommand.Action.(func(context *cli.Context) error)
assert.True(ok)
err = fn(ctx)
assert.Nil(err)
// With template
if tc.NotValid(ktu.NeedRoot()) {
t.Skip(testDisabledAsNonRoot)
}
runtimeConfig.FactoryConfig.Template = true
runtimeConfig.FactoryConfig.TemplatePath = "/run/vc/vm/template"
runtimeConfig.HypervisorType = vc.MockHypervisor
ctx.App.Metadata["runtimeConfig"] = runtimeConfig
fn, ok = initFactoryCommand.Action.(func(context *cli.Context) error)
assert.True(ok)
// config mock agent
stdCtx, err := cliContextToContext(ctx)
if err != nil {
stdCtx = context.Background()
}
stdCtx = vc.WithNewAgentFunc(stdCtx, vc.NewMockAgent)
ctx.App.Metadata["context"] = stdCtx
err = fn(ctx)
assert.Nil(err)
}
func TestFactoryCLIFunctionDestroy(t *testing.T) {
assert := assert.New(t)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
set := flag.NewFlagSet("", 0)
set.String("console-socket", "", "")
ctx := createCLIContext(set)
ctx.App.Name = "foo"
// No template
ctx.App.Metadata["runtimeConfig"] = runtimeConfig
fn, ok := destroyFactoryCommand.Action.(func(context *cli.Context) error)
assert.True(ok)
err = fn(ctx)
assert.Nil(err)
// With template
runtimeConfig.FactoryConfig.Template = true
runtimeConfig.HypervisorType = vc.MockHypervisor
ctx.App.Metadata["runtimeConfig"] = runtimeConfig
fn, ok = destroyFactoryCommand.Action.(func(context *cli.Context) error)
assert.True(ok)
err = fn(ctx)
assert.Nil(err)
}
func TestFactoryCLIFunctionStatus(t *testing.T) {
assert := assert.New(t)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
set := flag.NewFlagSet("", 0)
set.String("console-socket", "", "")
ctx := createCLIContext(set)
ctx.App.Name = "foo"
// No template
ctx.App.Metadata["runtimeConfig"] = runtimeConfig
fn, ok := statusFactoryCommand.Action.(func(context *cli.Context) error)
assert.True(ok)
err = fn(ctx)
assert.Nil(err)
// With template
runtimeConfig.FactoryConfig.Template = true
runtimeConfig.HypervisorType = vc.MockHypervisor
ctx.App.Metadata["runtimeConfig"] = runtimeConfig
err = fn(ctx)
assert.Nil(err)
}

View File

@@ -0,0 +1,540 @@
// Copyright (c) 2017-2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
// Note: To add a new architecture, implement all identifiers beginning "arch".
package main
/*
#include <linux/kvm.h>
const int ioctl_KVM_CREATE_VM = KVM_CREATE_VM;
const int ioctl_KVM_CHECK_EXTENSION = KVM_CHECK_EXTENSION;
*/
import "C"
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"syscall"
"github.com/kata-containers/kata-containers/src/runtime/pkg/katautils"
vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/oci"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
type kernelModule struct {
// maps parameter names to values
parameters map[string]string
// description
desc string
// if it is definitely required
required bool
}
// nolint: structcheck, unused, deadcode
type kvmExtension struct {
// description
desc string
// extension identifier
id int
}
type vmContainerCapableDetails struct {
requiredCPUFlags map[string]string
requiredCPUAttribs map[string]string
requiredKernelModules map[string]kernelModule
cpuInfoFile string
}
const (
moduleParamDir = "parameters"
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"
// these refer to fields in the procCPUINFO file
genericCPUFlagsTag = "flags" // nolint: varcheck, unused, deadcode
genericCPUVendorField = "vendor_id" // nolint: varcheck, unused, deadcode
genericCPUModelField = "model name" // nolint: varcheck, unused, deadcode
// If set, do not perform any network checks
noNetworkEnvVar = "KATA_CHECK_NO_NETWORK"
)
// variables rather than consts to allow tests to modify them
var (
procCPUInfo = "/proc/cpuinfo"
sysModuleDir = "/sys/module"
modProbeCmd = "modprobe"
)
// variables rather than consts to allow tests to modify them
var (
kvmDevice = "/dev/kvm"
)
// getCPUInfo returns details of the first CPU read from the specified cpuinfo file
func getCPUInfo(cpuInfoFile string) (string, error) {
text, err := katautils.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 {
kmodLog := kataLog.WithField("module", module)
// First, check to see if the module is already loaded
path := filepath.Join(sysModuleDir, module)
if katautils.FileExists(path) {
return true
}
// Only root can load modules
if os.Getuid() != 0 {
kmodLog.Error("Module is not loaded and it can not be inserted. Please consider running with sudo or as root")
return false
}
// Now, check if the module is unloaded, but available.
// And modprobe it if so.
cmd := exec.Command(modProbeCmd, module)
if output, err := cmd.CombinedOutput(); err != nil {
kmodLog.WithError(err).WithField("output", string(output)).Warnf("modprobe insert module failed")
return false
}
return true
}
// 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).Errorf("kernel property %s not found", module)
if details.required {
count++
}
continue
}
kataLog.WithFields(fields).Infof("kernel property found")
for param, expected := range details.parameters {
path := filepath.Join(sysModuleDir, module, moduleParamDir, param)
value, err := katautils.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
}
// genericHostIsVMContainerCapable checks to see if the host is theoretically capable
// of creating a VM container.
//nolint: unused,deadcode
func genericHostIsVMContainerCapable(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: "check",
Aliases: []string{"kata-check"},
Usage: "tests if system can run " + project,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "check-version-only",
Usage: "Only compare the current and latest available versions (requires network, non-root only)",
},
cli.BoolFlag{
Name: "include-all-releases",
Usage: "Don't filter out pre-release release versions",
},
cli.BoolFlag{
Name: "no-network-checks, n",
Usage: "Do not run any checks using the network",
},
cli.BoolFlag{
Name: "only-list-releases",
Usage: "Only list newer available releases (non-root only)",
},
cli.BoolFlag{
Name: "strict, s",
Usage: "perform strict checking",
},
cli.BoolFlag{
Name: "verbose, v",
Usage: "display the list of checks performed",
},
},
Description: fmt.Sprintf(`tests if system can run %s and version is current.
ENVIRONMENT VARIABLES:
- %s: If set to any value, act as if "--no-network-checks" was specified.
EXAMPLES:
- Perform basic checks:
$ %s check
- Local basic checks only:
$ %s check --no-network-checks
- Perform further checks:
$ sudo %s check
- Just check if a newer version is available:
$ %s check --check-version-only
- List available releases (shows output in format "version;release-date;url"):
$ %s check --only-list-releases
- List all available releases (includes pre-release versions):
$ %s check --only-list-releases --include-all-releases
`,
project,
noNetworkEnvVar,
name,
name,
name,
name,
name,
name,
),
Action: func(context *cli.Context) error {
verbose := context.Bool("verbose")
if verbose {
kataLog.Logger.SetLevel(logrus.InfoLevel)
}
if !context.Bool("no-network-checks") && os.Getenv(noNetworkEnvVar) == "" {
cmd := RelCmdCheck
if context.Bool("only-list-releases") {
cmd = RelCmdList
}
if os.Geteuid() == 0 {
kataLog.Warn("Not running network checks as super user")
} else {
err := HandleReleaseVersions(cmd, version, context.Bool("include-all-releases"))
if err != nil {
return err
}
}
}
if context.Bool("check-version-only") || context.Bool("only-list-releases") {
return nil
}
runtimeConfig, ok := context.App.Metadata["runtimeConfig"].(oci.RuntimeConfig)
if !ok {
return errors.New("check: cannot determine runtime config")
}
err := setCPUtype(runtimeConfig.HypervisorType)
if err != nil {
return err
}
details := vmContainerCapableDetails{
cpuInfoFile: procCPUInfo,
requiredCPUFlags: archRequiredCPUFlags,
requiredCPUAttribs: archRequiredCPUAttribs,
requiredKernelModules: archRequiredKernelModules,
}
err = hostIsVMContainerCapable(details)
if err != nil {
return err
}
fmt.Println(successMessageCapable)
if os.Geteuid() == 0 {
err = archHostCanCreateVMContainer(runtimeConfig.HypervisorType)
if err != nil {
return err
}
fmt.Println(successMessageCreate)
}
return nil
},
}
func genericArchKernelParamHandler(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
}
// genericKvmIsUsable determines if it will be possible to create a full virtual machine
// by creating a minimal VM and then deleting it.
func genericKvmIsUsable() 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
}
// genericCheckKVMExtension allows to query about the specific kvm extensions
// nolint: unused, deadcode
func genericCheckKVMExtensions(extensions map[string]kvmExtension) (map[string]int, error) {
results := make(map[string]int)
flags := syscall.O_RDWR | syscall.O_CLOEXEC
kvm, err := syscall.Open(kvmDevice, flags, 0)
if err != nil {
return results, err
}
defer syscall.Close(kvm)
for name, extension := range extensions {
fields := logrus.Fields{
"type": "kvm extension",
"name": name,
"description": extension.desc,
"id": extension.id,
}
ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL,
uintptr(kvm),
uintptr(C.ioctl_KVM_CHECK_EXTENSION),
uintptr(extension.id))
// Generally return value(ret) 0 means no and 1 means yes,
// but some extensions may report additional information in the integer return value.
if errno != 0 {
kataLog.WithFields(fields).Error("is not supported")
return results, errno
}
results[name] = int(ret)
kataLog.WithFields(fields).Info("kvm extension is supported")
}
return results, nil
}

View File

@@ -0,0 +1,324 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io/ioutil"
"strings"
"syscall"
"unsafe"
vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers"
"github.com/sirupsen/logrus"
)
const (
cpuFlagsTag = genericCPUFlagsTag
archCPUVendorField = genericCPUVendorField
archCPUModelField = genericCPUModelField
archGenuineIntel = "GenuineIntel"
archAuthenticAMD = "AuthenticAMD"
msgKernelVM = "Kernel-based Virtual Machine"
msgKernelVirtio = "Host kernel accelerator for virtio"
msgKernelVirtioNet = "Host kernel accelerator for virtio network"
msgKernelVirtioVhostVsock = "Host Support for Linux VM Sockets"
cpuFlagVMX = "vmx"
cpuFlagLM = "lm"
cpuFlagSVM = "svm"
cpuFlagSSE4_1 = "sse4_1"
kernelModvhm = "vhm_dev"
kernelModvhost = "vhost"
kernelModvhostnet = "vhost_net"
kernelModvhostvsock = "vhost_vsock"
kernelModkvm = "kvm"
kernelModkvmintel = "kvm_intel"
kernelModkvmamd = "kvm_amd"
)
// CPU types
const (
cpuTypeIntel = 0
cpuTypeAMD = 1
cpuTypeUnknown = -1
)
const acrnDevice = "/dev/acrn_vhm"
// ioctl_ACRN_CREATE_VM is the IOCTL to create VM in ACRN.
// Current Linux mainstream kernel doesn't have support for ACRN.
// Due to this several macros are not defined in Linux headers.
// Until the support is available, directly use the value instead
// of macros.
//https://github.com/kata-containers/runtime/issues/1784
const ioctl_ACRN_CREATE_VM = 0x43000010 //nolint
const ioctl_ACRN_DESTROY_VM = 0x43000011 //nolint
type acrn_create_vm struct { //nolint
vmid uint16 //nolint
reserved0 uint16 //nolint
vcpu_num uint16 //nolint
reserved1 uint16 //nolint
uuid [16]uint8
vm_flag uint64 //nolint
req_buf uint64 //nolint
reserved2 [16]uint8 //nolint
}
// cpuType save the CPU type
var cpuType int
// archRequiredCPUFlags maps a CPU flag value to search for and a
// human-readable description of that value.
var archRequiredCPUFlags map[string]string
// 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
// 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
func setCPUtype(hypervisorType vc.HypervisorType) error {
cpuType = getCPUtype()
if cpuType == cpuTypeUnknown {
return fmt.Errorf("Unknow CPU Type")
} else if cpuType == cpuTypeIntel {
var kvmIntelParams map[string]string
onVMM, err := vc.RunningOnVMM(procCPUInfo)
if err != nil && !onVMM {
kvmIntelParams = map[string]string{
// "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",
}
}
switch hypervisorType {
case "firecracker":
fallthrough
case "clh":
fallthrough
case "qemu":
archRequiredCPUFlags = map[string]string{
cpuFlagVMX: "Virtualization support",
cpuFlagLM: "64Bit CPU",
cpuFlagSSE4_1: "SSE4.1",
}
archRequiredCPUAttribs = map[string]string{
archGenuineIntel: "Intel Architecture CPU",
}
archRequiredKernelModules = map[string]kernelModule{
kernelModkvm: {
desc: msgKernelVM,
required: true,
},
kernelModkvmintel: {
desc: "Intel KVM",
parameters: kvmIntelParams,
required: true,
},
kernelModvhost: {
desc: msgKernelVirtio,
required: true,
},
kernelModvhostnet: {
desc: msgKernelVirtioNet,
required: true,
},
kernelModvhostvsock: {
desc: msgKernelVirtioVhostVsock,
required: false,
},
}
case "acrn":
archRequiredCPUFlags = map[string]string{
cpuFlagLM: "64Bit CPU",
cpuFlagSSE4_1: "SSE4.1",
}
archRequiredCPUAttribs = map[string]string{
archGenuineIntel: "Intel Architecture CPU",
}
archRequiredKernelModules = map[string]kernelModule{
kernelModvhm: {
desc: "Intel ACRN",
required: false,
},
kernelModvhost: {
desc: msgKernelVirtio,
required: false,
},
kernelModvhostnet: {
desc: msgKernelVirtioNet,
required: false,
},
}
case "mock":
archRequiredCPUFlags = map[string]string{
cpuFlagVMX: "Virtualization support",
cpuFlagLM: "64Bit CPU",
cpuFlagSSE4_1: "SSE4.1",
}
archRequiredCPUAttribs = map[string]string{
archGenuineIntel: "Intel Architecture CPU",
}
default:
return fmt.Errorf("setCPUtype: Unknown hypervisor type %s", hypervisorType)
}
} else if cpuType == cpuTypeAMD {
archRequiredCPUFlags = map[string]string{
cpuFlagSVM: "Virtualization support",
cpuFlagLM: "64Bit CPU",
cpuFlagSSE4_1: "SSE4.1",
}
archRequiredCPUAttribs = map[string]string{
archAuthenticAMD: "AMD Architecture CPU",
}
archRequiredKernelModules = map[string]kernelModule{
kernelModkvm: {
desc: msgKernelVM,
required: true,
},
kernelModkvmamd: {
desc: "AMD KVM",
required: true,
},
kernelModvhost: {
desc: msgKernelVirtio,
required: true,
},
kernelModvhostnet: {
desc: msgKernelVirtioNet,
required: true,
},
kernelModvhostvsock: {
desc: msgKernelVirtioVhostVsock,
required: false,
},
}
}
return nil
}
func getCPUtype() int {
content, err := ioutil.ReadFile("/proc/cpuinfo")
if err != nil {
kataLog.WithError(err).Error("failed to read file")
return cpuTypeUnknown
}
str := string(content)
if strings.Contains(str, archGenuineIntel) {
return cpuTypeIntel
} else if strings.Contains(str, archAuthenticAMD) {
return cpuTypeAMD
} else {
return cpuTypeUnknown
}
}
// 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 {
return genericKvmIsUsable()
}
// acrnIsUsable determines if it will be possible to create a full virtual machine
// by creating a minimal VM and then deleting it.
func acrnIsUsable() error {
flags := syscall.O_RDWR | syscall.O_CLOEXEC
f, err := syscall.Open(acrnDevice, flags, 0)
if err != nil {
return err
}
defer syscall.Close(f)
kataLog.WithField("device", acrnDevice).Info("device available")
acrnInst := vc.Acrn{}
uuidStr, err := acrnInst.GetNextAvailableUUID()
if err != nil {
return err
}
uuid, err := acrnInst.GetACRNUUIDBytes(uuidStr)
if err != nil {
return fmt.Errorf("Converting UUID str to bytes failed, Err:%s", err)
}
var createVM acrn_create_vm
createVM.uuid = uuid
ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL,
uintptr(f),
uintptr(ioctl_ACRN_CREATE_VM),
uintptr(unsafe.Pointer(&createVM)))
if ret != 0 || errno != 0 {
if errno == syscall.EBUSY {
kataLog.WithField("reason", "another hypervisor running").Error("cannot create VM")
}
kataLog.WithFields(logrus.Fields{
"ret": ret,
"errno": errno,
}).Info("Create VM Error")
return errno
}
ret, _, errno = syscall.Syscall(syscall.SYS_IOCTL,
uintptr(f),
uintptr(ioctl_ACRN_DESTROY_VM),
0)
if ret != 0 || errno != 0 {
kataLog.WithFields(logrus.Fields{
"ret": ret,
"errno": errno,
}).Info("Destroy VM Error")
return errno
}
kataLog.WithField("feature", "create-vm").Info("feature available")
return nil
}
func archHostCanCreateVMContainer(hypervisorType vc.HypervisorType) error {
switch hypervisorType {
case "qemu":
fallthrough
case "clh":
fallthrough
case "firecracker":
return kvmIsUsable()
case "acrn":
return acrnIsUsable()
case "mock":
return nil
default:
return fmt.Errorf("archHostCanCreateVMContainer: Unknown hypervisor type %s", hypervisorType)
}
}
// hostIsVMContainerCapable checks to see if the host is theoretically capable
// of creating a VM container.
func hostIsVMContainerCapable(details vmContainerCapableDetails) error {
return genericHostIsVMContainerCapable(details)
}
func archKernelParamHandler(onVMM bool, fields logrus.Fields, msg string) bool {
return genericArchKernelParamHandler(onVMM, fields, msg)
}
func getCPUDetails() (vendor, model string, err error) {
return genericGetCPUDetails()
}

View File

@@ -0,0 +1,492 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"testing"
ktu "github.com/kata-containers/kata-containers/src/runtime/pkg/katatestutils"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
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) {
if tc.NotValid(ktu.NeedRoot()) {
t.Skip(testDisabledAsNonRoot)
}
var cpuData []testCPUData
var moduleData []testModuleData
if cpuType == cpuTypeIntel {
cpuData = []testCPUData{
{archGenuineIntel, "lm vmx sse4_1", false},
}
moduleData = []testModuleData{}
} else if cpuType == cpuTypeAMD {
cpuData = []testCPUData{
{archAuthenticAMD, "lm svm sse4_1", false},
}
moduleData = []testModuleData{}
}
genericCheckCLIFunction(t, cpuData, moduleData)
}
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",
},
required: true,
},
}
actualModuleData := []testModuleData{
{filepath.Join(sysModuleDir, "kvm"), "", true},
{filepath.Join(sysModuleDir, "kvm_intel"), "", true},
{filepath.Join(sysModuleDir, "kvm_intel/parameters/unrestricted_guest"), "Y", false},
// XXX: force a warning
{filepath.Join(sysModuleDir, "kvm_intel/parameters/nested"), "N", false},
}
vendor := archGenuineIntel
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(`.*\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",
},
required: true,
},
}
actualModuleData := []testModuleData{
{filepath.Join(sysModuleDir, "kvm"), "", true},
{filepath.Join(sysModuleDir, "kvm_intel"), "", true},
{filepath.Join(sysModuleDir, "kvm_intel/parameters/nested"), "Y", false},
// XXX: force a failure on non-VMM systems
{filepath.Join(sysModuleDir, "kvm_intel/parameters/unrestricted_guest"), "N", false},
}
vendor := archGenuineIntel
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(`.*\bunrestricted_guest\b`)
matches := re.FindAllStringSubmatch(buf.String(), -1)
assert.NotEmpty(matches)
}
func TestCheckHostIsVMContainerCapable(t *testing.T) {
if tc.NotValid(ktu.NeedRoot()) {
t.Skip(testDisabledAsNonRoot)
}
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)
}
var cpuData []testCPUData
var moduleData []testModuleData
if cpuType == cpuTypeIntel {
cpuData = []testCPUData{
{"", "", true},
{"Intel", "", true},
{archGenuineIntel, "", true},
{archGenuineIntel, "lm", true},
{archGenuineIntel, "lm vmx", true},
{archGenuineIntel, "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"), "Y", false},
{filepath.Join(sysModuleDir, "kvm_intel/parameters/unrestricted_guest"), "Y", false},
}
} else if cpuType == cpuTypeAMD {
cpuData = []testCPUData{
{"", "", true},
{"AMD", "", true},
{archAuthenticAMD, "", true},
{archAuthenticAMD, "lm", true},
{archAuthenticAMD, "lm svm", true},
{archAuthenticAMD, "lm svm sse4_1", false},
}
moduleData = []testModuleData{
{filepath.Join(sysModuleDir, "kvm"), "", true},
{filepath.Join(sysModuleDir, "kvm_amd"), "", true},
{filepath.Join(sysModuleDir, "kvm_amd/parameters/nested"), "1", false},
}
}
// to check if host is capable for Kata Containers, must setup CPU info first.
_, config, err := makeRuntimeConfig(dir)
assert.NoError(err)
setCPUtype(config.HypervisorType)
setupCheckHostIsVMContainerCapable(assert, cpuInfoFile, cpuData, moduleData)
details := vmContainerCapableDetails{
cpuInfoFile: cpuInfoFile,
requiredCPUFlags: archRequiredCPUFlags,
requiredCPUAttribs: archRequiredCPUAttribs,
requiredKernelModules: archRequiredKernelModules,
}
err = hostIsVMContainerCapable(details)
assert.Nil(err)
// remove the modules to force a failure
err = os.RemoveAll(sysModuleDir)
assert.NoError(err)
err = hostIsVMContainerCapable(details)
assert.Error(err)
}
func TestArchKernelParamHandler(t *testing.T) {
assert := assert.New(t)
type testData struct {
fields logrus.Fields
msg string
onVMM bool
expectIgnore bool
}
data := []testData{
{logrus.Fields{}, "", true, false},
{logrus.Fields{}, "", false, false},
{
logrus.Fields{
// wrong type
"parameter": 123,
},
"foo",
false,
false,
},
{
logrus.Fields{
"parameter": "unrestricted_guest",
},
"",
false,
false,
},
{
logrus.Fields{
"parameter": "unrestricted_guest",
},
"",
true,
true,
},
{
logrus.Fields{
"parameter": "nested",
},
"",
false,
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)
}
func TestGetCPUDetails(t *testing.T) {
const validVendorName = "a vendor"
validVendor := fmt.Sprintf(`%s : %s`, archCPUVendorField, validVendorName)
const validModelName = "some CPU model"
validModel := fmt.Sprintf(`%s : %s`, archCPUModelField, validModelName)
validContents := fmt.Sprintf(`
a : b
%s
foo : bar
%s
`, validVendor, validModel)
data := []testCPUDetail{
{"", "", "", true},
{"invalid", "", "", true},
{archCPUVendorField, "", "", true},
{validVendor, "", "", true},
{validModel, "", "", true},
{validContents, validVendorName, validModelName, false},
}
genericTestGetCPUDetails(t, validVendor, validModel, validContents, data)
}
func TestSetCPUtype(t *testing.T) {
assert := assert.New(t)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
savedArchRequiredCPUFlags := archRequiredCPUFlags
savedArchRequiredCPUAttribs := archRequiredCPUAttribs
savedArchRequiredKernelModules := archRequiredKernelModules
defer func() {
archRequiredCPUFlags = savedArchRequiredCPUFlags
archRequiredCPUAttribs = savedArchRequiredCPUAttribs
archRequiredKernelModules = savedArchRequiredKernelModules
}()
archRequiredCPUFlags = map[string]string{}
archRequiredCPUAttribs = map[string]string{}
archRequiredKernelModules = map[string]kernelModule{}
_, config, err := makeRuntimeConfig(tmpdir)
assert.NoError(err)
setCPUtype(config.HypervisorType)
assert.NotEmpty(archRequiredCPUFlags)
assert.NotEmpty(archRequiredCPUAttribs)
assert.NotEmpty(archRequiredKernelModules)
assert.Equal(archRequiredCPUFlags["vmx"], "Virtualization support")
_, ok := archRequiredKernelModules["kvm"]
assert.True(ok)
}

View File

@@ -0,0 +1,171 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers"
"github.com/sirupsen/logrus"
)
const (
cpuFlagsTag = "Features"
archCPUVendorField = "CPU implementer"
archCPUModelField = "CPU architecture"
)
// archRequiredCPUFlags maps a CPU flag value to search for and a
// human-readable description of that value.
var archRequiredCPUFlags = map[string]string{}
// 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{}
// 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",
required: true,
},
"vhost": {
desc: "Host kernel accelerator for virtio",
required: true,
},
"vhost_net": {
desc: "Host kernel accelerator for virtio network",
required: true,
},
"vhost_vsock": {
desc: "Host Support for Linux VM Sockets",
required: false,
},
}
// archRequiredKVMExtensions maps a required kvm extension to a human-readable
// description of what this extension intends to do and its unique identifier.
var archRequiredKVMExtensions = map[string]kvmExtension{
"KVM_CAP_ARM_VM_IPA_SIZE": {
desc: "Maximum IPA shift supported by the host",
id: 165,
},
}
func setCPUtype(hypervisorType vc.HypervisorType) error {
return nil
}
// 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 {
return genericKvmIsUsable()
}
func checkKVMExtensions() error {
results, err := genericCheckKVMExtensions(archRequiredKVMExtensions)
if err != nil {
return err
}
// different host supports different maximum IPA limit
ipa := results["KVM_CAP_ARM_VM_IPA_SIZE"]
fields := logrus.Fields{
"type": "kvm extension",
"name": "KVM_CAP_ARM_VM_IPA_SIZE",
}
kataLog.WithFields(fields).Infof("IPA limit size: %d bits.", ipa)
return nil
}
func archHostCanCreateVMContainer(hypervisorType vc.HypervisorType) error {
if err := kvmIsUsable(); err != nil {
return err
}
return checkKVMExtensions()
}
// hostIsVMContainerCapable checks to see if the host is theoretically capable
// of creating a VM container.
func hostIsVMContainerCapable(details vmContainerCapableDetails) error {
_, err := getCPUInfo(details.cpuInfoFile)
if err != nil {
return err
}
count, err := checkKernelModules(details.requiredKernelModules, archKernelParamHandler)
if err != nil {
return err
}
if count == 0 {
return nil
}
return fmt.Errorf("ERROR: %s", failMessage)
}
func archKernelParamHandler(onVMM bool, fields logrus.Fields, msg string) bool {
return genericArchKernelParamHandler(onVMM, fields, msg)
}
// The CPU Vendor here for Arm means the CPU core
// IP Implementer.
// normalizeArmVendor maps 'CPU implementer' in /proc/cpuinfo
// to human-readable description of that value.
func normalizeArmVendor(vendor string) string {
switch vendor {
case "0x41":
vendor = "ARM Limited"
default:
vendor = "3rd Party Limited"
}
return vendor
}
// The CPU Model here for Arm means the Instruction set, that is
// the variant number of Arm processor.
// normalizeArmModel maps 'CPU architecture' in /proc/cpuinfo
// to human-readable description of that value.
func normalizeArmModel(model string) string {
switch model {
case "8":
model = "v8"
case "7", "7M", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
model = "v7"
case "6", "6TEJ":
model = "v6"
case "5", "5T", "5TE", "5TEJ":
model = "v5"
case "4", "4T":
model = "v4"
case "3":
model = "v3"
default:
model = "unknown"
}
return model
}
func getCPUDetails() (string, string, error) {
vendor, model, err := genericGetCPUDetails()
if err == nil {
vendor = normalizeArmVendor(vendor)
model = normalizeArmModel(model)
}
return vendor, model, err
}

View File

@@ -0,0 +1,112 @@
// Copyright (c) 2018 ARM Limited
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func setupCheckHostIsVMContainerCapable(assert *assert.Assertions, cpuInfoFile string, cpuData []testCPUData, moduleData []testModuleData) {
//For now, Arm64 only deal with module check
_ = cpuData
createModules(assert, cpuInfoFile, moduleData)
err := makeCPUInfoFile(cpuInfoFile, "", "")
assert.NoError(err)
}
func TestCCCheckCLIFunction(t *testing.T) {
var cpuData []testCPUData
moduleData := []testModuleData{
{filepath.Join(sysModuleDir, "kvm"), true, ""},
{filepath.Join(sysModuleDir, "vhost"), true, ""},
{filepath.Join(sysModuleDir, "vhost_net"), true, ""},
}
genericCheckCLIFunction(t, cpuData, moduleData)
}
func TestGetCPUDetails(t *testing.T) {
type testData struct {
contents string
expectedNormalizeVendor string
expectedNormalizeModel string
expectError bool
}
validVendorName := "0x41"
validNormalizeVendorName := "ARM Limited"
validVendor := fmt.Sprintf(`%s : %s`, archCPUVendorField, validVendorName)
validModelName := "8"
validNormalizeModelName := "v8"
validModel := fmt.Sprintf(`%s : %s`, archCPUModelField, validModelName)
validContents := fmt.Sprintf(`
a : b
%s
foo : bar
%s
`, validVendor, validModel)
data := []testData{
{"", "", "", true},
{"invalid", "", "", true},
{archCPUVendorField, "", "", true},
{validVendor, "", "", true},
{validModel, "", "", true},
{validContents, validNormalizeVendorName, validNormalizeModelName, false},
}
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
panic(err)
}
defer os.RemoveAll(tmpdir)
savedProcCPUInfo := procCPUInfo
testProcCPUInfo := filepath.Join(tmpdir, "cpuinfo")
// override
procCPUInfo = testProcCPUInfo
defer func() {
procCPUInfo = savedProcCPUInfo
}()
_, _, 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.expectedNormalizeVendor, vendor)
assert.Equal(t, d.expectedNormalizeModel, model)
}
}
}
func TestSetCPUtype(t *testing.T) {
testSetCPUTypeGeneric(t)
}

View File

@@ -0,0 +1,63 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
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:
`

View File

@@ -0,0 +1,27 @@
// Copyright (c) 2018 ARM Limited
//
// SPDX-License-Identifier: Apache-2.0
//
package main
const testCPUInfoTemplate = `
processor : 0
BogoMIPS : 500.00
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x1
CPU part : 0xd07
CPU revision : 2
processor : 1
BogoMIPS : 500.00
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x1
CPU part : 0xd07
CPU revision : 2
`

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2018 IBM
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"github.com/sirupsen/logrus"
"io/ioutil"
)
var testCPUInfoTemplate = setTestCPUInfoTemplate()
func setTestCPUInfoTemplate() string {
var kataLog *logrus.Entry
content, err := ioutil.ReadFile("/proc/cpuinfo")
if err != nil {
kataLog.WithError(err).Error("failed to read file /proc/cpuinfo")
}
return string(content)
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) 2018 IBM
//
// SPDX-License-Identifier: Apache-2.0
//
package main
const testCPUInfoTemplate = `
vendor_id : IBM/S390
# processors : 4
bogomips per cpu: 20325.00
max thread id : 0
features : esan3 zarch stfle msa ldisp eimm dfp edat etf3eh highgprs te vx sie
cache0 : level=1 type=Data scope=Private size=128K line_size=256 associativity=8
cache1 : level=1 type=Instruction scope=Private size=96K line_size=256 associativity=6
cache2 : level=2 type=Data scope=Private size=2048K line_size=256 associativity=8
cache3 : level=2 type=Instruction scope=Private size=2048K line_size=256 associativity=8
cache4 : level=3 type=Unified scope=Shared size=65536K line_size=256 associativity=16
cache5 : level=4 type=Unified scope=Shared size=491520K line_size=256 associativity=30
processor 0: version = FF, identification = FFFFFF, machine = 2964
`

View File

@@ -0,0 +1,47 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
// +build arm64 ppc64le
package main
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func testSetCPUTypeGeneric(t *testing.T) {
assert := assert.New(t)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
savedArchRequiredCPUFlags := archRequiredCPUFlags
savedArchRequiredCPUAttribs := archRequiredCPUAttribs
savedArchRequiredKernelModules := archRequiredKernelModules
defer func() {
archRequiredCPUFlags = savedArchRequiredCPUFlags
archRequiredCPUAttribs = savedArchRequiredCPUAttribs
archRequiredKernelModules = savedArchRequiredKernelModules
}()
assert.Empty(archRequiredCPUFlags)
assert.Empty(archRequiredCPUAttribs)
assert.NotEmpty(archRequiredKernelModules)
_, config, err := makeRuntimeConfig(tmpdir)
assert.NoError(err)
setCPUtype(config.HypervisorType)
assert.Equal(archRequiredCPUFlags, savedArchRequiredCPUFlags)
assert.Equal(archRequiredCPUAttribs, savedArchRequiredCPUAttribs)
assert.Equal(archRequiredKernelModules, savedArchRequiredKernelModules)
}

View File

@@ -0,0 +1,190 @@
// Copyright (c) 2018 IBM
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
"github.com/kata-containers/kata-containers/src/runtime/pkg/katautils"
vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers"
"github.com/sirupsen/logrus"
)
const (
cpuFlagsTag = genericCPUFlagsTag
archCPUVendorField = ""
archCPUModelField = "model"
)
var (
ppc64CpuCmd = "ppc64_cpu"
smtStatusOption = "--smt"
_ = genericCPUVendorField
_ = genericCPUModelField
)
// archRequiredCPUFlags maps a CPU flag value to search for and a
// human-readable description of that value.
var archRequiredCPUFlags = map[string]string{}
// 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{}
// 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",
required: true,
},
"kvm_hv": {
desc: "Kernel-based Virtual Machine hardware virtualization",
required: true,
},
"vhost_vsock": {
desc: "Host Support for Linux VM Sockets",
required: false,
},
}
func setCPUtype(hypervisorType vc.HypervisorType) error {
return nil
}
func archHostCanCreateVMContainer(hypervisorType vc.HypervisorType) error {
return kvmIsUsable()
}
// hostIsVMContainerCapable checks to see if the host is theoretically capable
// of creating a VM container.
func hostIsVMContainerCapable(details vmContainerCapableDetails) error {
_, err := getCPUInfo(details.cpuInfoFile)
if err != nil {
return err
}
text, err := katautils.GetFileContents(details.cpuInfoFile)
if err != nil {
return err
}
ae := regexp.MustCompile("[0-9]+")
re := regexp.MustCompile("POWER[0-9]")
powerProcessor, err := strconv.Atoi(ae.FindString(re.FindString(text)))
if err != nil {
kataLog.WithError(err).Error("Failed to find Power Processor number from ", details.cpuInfoFile)
}
if powerProcessor <= 8 {
if !isSMTOff() {
return fmt.Errorf("SMT is not Off. %s", failMessage)
}
}
count, err := checkKernelModules(details.requiredKernelModules, archKernelParamHandler)
if err != nil {
return err
}
if count == 0 {
return nil
}
return fmt.Errorf("ERROR: %s", failMessage)
}
// 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 {
return genericKvmIsUsable()
}
func archKernelParamHandler(onVMM bool, fields logrus.Fields, msg string) bool {
return genericArchKernelParamHandler(onVMM, fields, msg)
}
func getPPC64leCPUInfo(cpuInfoFile string) (string, error) {
text, err := katautils.GetFileContents(cpuInfoFile)
if err != nil {
return "", err
}
if len(strings.TrimSpace(text)) == 0 {
return "", fmt.Errorf("Cannot determine CPU details")
}
return text, nil
}
func getCPUDetails() (vendor, model string, err error) {
if vendor, model, err := genericGetCPUDetails(); err == nil {
return vendor, model, nil
}
cpuinfo, err := getPPC64leCPUInfo(procCPUInfo)
if err != nil {
return "", "", err
}
lines := strings.Split(cpuinfo, "\n")
for _, line := range lines {
if archCPUVendorField != "" {
if strings.HasPrefix(line, archCPUVendorField) {
fields := strings.Split(line, ":")
if len(fields) > 1 {
vendor = strings.TrimSpace(fields[1])
}
}
}
if archCPUModelField != "" {
if strings.HasPrefix(line, archCPUModelField) {
fields := strings.Split(line, ":")
if len(fields) > 1 {
model = strings.TrimSpace(fields[1])
}
}
}
}
if archCPUVendorField != "" && vendor == "" {
return "", "", fmt.Errorf("cannot find vendor field in file %v", procCPUInfo)
}
if archCPUModelField != "" && model == "" {
return "", "", fmt.Errorf("cannot find model field in file %v", procCPUInfo)
}
return vendor, model, nil
}
func isSMTOff() bool {
// Check if the SMT is available and off
cmd := exec.Command(ppc64CpuCmd, smtStatusOption)
additionalEnv := "LANG=C"
cmd.Env = append(cmd.Env, additionalEnv)
out, err := cmd.Output()
if err == nil && strings.TrimRight(string(out), "\n") == "SMT is off" {
return true
} else if err != nil {
kataLog.Warn("ppc64_cpu isn't installed, can't detect SMT")
return true
}
return false
}

View File

@@ -0,0 +1,175 @@
// Copyright (c) 2018 IBM
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
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) {
cpuData := []testCPUData{
fakeCPUData,
}
moduleData := []testModuleData{
{filepath.Join(sysModuleDir, "kvm"), false, "Y"},
{filepath.Join(sysModuleDir, "kvm_hv"), false, "Y"},
}
genericCheckCLIFunction(t, cpuData, moduleData)
}
func TestArchKernelParamHandler(t *testing.T) {
assert := assert.New(t)
type testData struct {
onVMM bool
expectIgnore bool
fields logrus.Fields
msg string
}
data := []testData{
{true, false, logrus.Fields{}, ""},
{false, false, logrus.Fields{}, ""},
{
false,
false,
logrus.Fields{
// wrong type
"parameter": 123,
},
"foo",
},
{
false,
false,
logrus.Fields{
"parameter": "unrestricted_guest",
},
"",
},
{
true,
true,
logrus.Fields{
"parameter": "unrestricted_guest",
},
"",
},
{
false,
true,
logrus.Fields{
"parameter": "nested",
},
"",
},
}
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)
}
func TestGetCPUDetails(t *testing.T) {
const validVendorName = ""
validVendor := fmt.Sprintf(`%s : %s`, archCPUVendorField, validVendorName)
const validModelName = "8247-22L"
validModel := fmt.Sprintf(`%s : %s`, archCPUModelField, validModelName)
validContents := fmt.Sprintf(`
a : b
%s
foo : bar
%s
`, validVendor, validModel)
data := []testCPUDetail{
{"", "", "", true},
{"invalid", "", "", true},
{archCPUVendorField, "", "", true},
{validVendor, "", "", true},
{validModel, "", validModelName, false},
{validContents, validVendorName, validModelName, false},
}
genericTestGetCPUDetails(t, validVendor, validModel, validContents, data)
}
func TestSetCPUtype(t *testing.T) {
testSetCPUTypeGeneric(t)
}

View File

@@ -0,0 +1,131 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"strings"
vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers"
"github.com/sirupsen/logrus"
)
const (
cpuFlagsTag = genericCPUFlagsTag
archCPUVendorField = genericCPUVendorField
// On s390x the cpu model is indicated by the field machine.
// Example:
// processor 0: version = FF, identification = 3FEC87, machine = 2964
archCPUModelField = "machine"
)
// archRequiredCPUFlags maps a CPU flag value to search for and a
// human-readable description of that value.
var archRequiredCPUFlags = map[string]string{}
// 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{}
// 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",
required: true,
},
"vhost_vsock": {
desc: "Host Support for Linux VM Sockets",
required: false,
},
}
func setCPUtype(hypervisorType vc.HypervisorType) error {
return nil
}
// 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 {
return genericKvmIsUsable()
}
func archHostCanCreateVMContainer(hypervisorType vc.HypervisorType) error {
return kvmIsUsable()
}
// hostIsVMContainerCapable checks to see if the host is theoretically capable
// of creating a VM container.
func hostIsVMContainerCapable(details vmContainerCapableDetails) error {
_, err := getCPUInfo(details.cpuInfoFile)
if err != nil {
return err
}
count, err := checkKernelModules(details.requiredKernelModules, archKernelParamHandler)
if err != nil {
return err
}
if count == 0 {
return nil
}
return fmt.Errorf("ERROR: %s", failMessage)
}
func archKernelParamHandler(onVMM bool, fields logrus.Fields, msg string) bool {
return genericArchKernelParamHandler(onVMM, fields, msg)
}
// getS390xCPUDetails returns the cpu information
func getS390xCPUDetails() (vendor, model string, err error) {
prefixModel := "processor"
cpuinfo, err := getCPUInfo(procCPUInfo)
if err != nil {
return "", "", err
}
lines := strings.Split(cpuinfo, "\n")
for _, line := range lines {
if archCPUVendorField != "" {
if strings.HasPrefix(line, archCPUVendorField) {
fields := strings.Split(line, ":")
if len(fields) > 1 {
vendor = strings.TrimSpace(fields[1])
}
}
}
if archCPUModelField != "" {
if strings.HasPrefix(line, prefixModel) {
fields := strings.Split(strings.TrimSpace(line), ",")
cpuModel := strings.Split(fields[2], "=")
model = strings.TrimSpace(cpuModel[1])
}
}
}
if vendor == "" {
return "", "", fmt.Errorf("cannot find vendor field in file %v", procCPUInfo)
}
if model == "" {
return "", "", fmt.Errorf("Error in parsing cpu model from %v", procCPUInfo)
}
return vendor, model, nil
}
func getCPUDetails() (vendor, model string, err error) {
if vendor, model, err := genericGetCPUDetails(); err == nil {
return vendor, model, nil
}
return getS390xCPUDetails()
}

View File

@@ -0,0 +1,168 @@
// Copyright (c) 2018 IBM
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
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) {
cpuData := []testCPUData{
fakeCPUData,
}
moduleData := []testModuleData{
{filepath.Join(sysModuleDir, "kvm"), false, "Y"},
}
genericCheckCLIFunction(t, cpuData, moduleData)
}
func TestArchKernelParamHandler(t *testing.T) {
assert := assert.New(t)
type testData struct {
onVMM bool
expectIgnore bool
fields logrus.Fields
msg string
}
data := []testData{
{true, false, logrus.Fields{}, ""},
{false, false, logrus.Fields{}, ""},
{
false,
false,
logrus.Fields{
// wrong type
"parameter": 123,
},
"foo",
},
{
false,
false,
logrus.Fields{
"parameter": "unrestricted_guest",
},
"",
},
{
true,
true,
logrus.Fields{
"parameter": "unrestricted_guest",
},
"",
},
{
false,
true,
logrus.Fields{
"parameter": "nested",
},
"",
},
}
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)
}
func TestGetCPUDetails(t *testing.T) {
const validVendorName = "a vendor"
validVendor := fmt.Sprintf(`%s : %s`, archCPUVendorField, validVendorName)
const validModelName = "some CPU model"
validModel := fmt.Sprintf(`processor 0: version = 00, identification = XXXXX, %s = %s`, archCPUModelField, validModelName)
validContents := fmt.Sprintf(`
a : b
%s
foo : bar
%s
`, validVendor, validModel)
data := []testCPUDetail{
{"", "", "", true},
{"invalid", "", "", true},
{archCPUVendorField, "", "", true},
{validVendor, "", "", true},
{validModel, "", "", true},
{validContents, validVendorName, validModelName, false},
}
genericTestGetCPUDetails(t, validVendor, validModel, validContents, data)
}

View File

@@ -0,0 +1,920 @@
// Copyright (c) 2017 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"bytes"
"flag"
"fmt"
"html/template"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"testing"
"github.com/kata-containers/kata-containers/src/runtime/pkg/katatestutils"
ktu "github.com/kata-containers/kata-containers/src/runtime/pkg/katatestutils"
"github.com/kata-containers/kata-containers/src/runtime/pkg/katautils"
vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
)
type testModuleData struct {
path string
contents string
isDir bool
}
// nolint: structcheck, unused, deadcode
type testCPUData struct {
vendorID string
flags string
expectError bool
}
// nolint: structcheck, unused, deadcode
type testCPUDetail struct {
contents string
expectedVendor string
expectedModel string
expectError bool
}
var fakeCPUData = testCPUData{"", "", false}
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 katautils.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)
}
// nolint: unused, deadcode
func genericTestGetCPUDetails(t *testing.T, validVendor string, validModel string, validContents string, data []testCPUDetail) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
panic(err)
}
defer os.RemoveAll(tmpdir)
savedProcCPUInfo := procCPUInfo
testProcCPUInfo := filepath.Join(tmpdir, "cpuinfo")
// override
procCPUInfo = testProcCPUInfo
defer func() {
procCPUInfo = savedProcCPUInfo
}()
_, _, 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 genericCheckCLIFunction(t *testing.T, cpuData []testCPUData, moduleData []testModuleData) {
assert := assert.New(t)
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
_, config, err := makeRuntimeConfig(dir)
assert.NoError(err)
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
}()
// Replace sysModuleDir in moduleData with the test temp path
for i := range moduleData {
moduleData[i].path = strings.Replace(moduleData[i].path, savedSysModuleDir, sysModuleDir, 1)
}
err = os.MkdirAll(sysModuleDir, testDirMode)
if err != nil {
t.Fatal(err)
}
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)
flagSet := &flag.FlagSet{}
ctx := createCLIContext(flagSet)
ctx.App.Name = "foo"
if katatestutils.IsInGitHubActions() {
// only set to mock if on GitHub
t.Logf("running tests under GitHub actions")
config.HypervisorType = vc.MockHypervisor
}
ctx.App.Metadata["runtimeConfig"] = config
// 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 {
if c == fakeCPUData {
continue
}
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 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", ""},
{":", ""},
{
cpuFlagsTag,
"",
},
{
cpuFlagsTag + ":",
"",
},
{
fmt.Sprintf("%s: a b c", cpuFlagsTag),
"a b c",
},
{
fmt.Sprintf("%s: a b c foo bar d", cpuFlagsTag),
"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 {
required map[string]string
cpuflags 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,
},
{
map[string]string{
"b": "B flag",
},
"a b c",
0,
},
{
map[string]string{
"x": "X flag",
"y": "Y flag",
"z": "Z flag",
},
"a b c",
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 {
required map[string]string
cpuinfo string
expectCount uint32
}
data := []testData{
{
map[string]string{},
"",
0,
},
{
map[string]string{
"a": "",
},
"",
0,
},
{
map[string]string{
"b": "B attribute",
},
"a: b",
0,
},
{
map[string]string{
"b": "B attribute",
},
"a: b\nc: d\ne: f",
0,
},
{
map[string]string{
"b": "B attribute",
"c": "C attribute",
"d": "D attribute",
},
"a: b\n",
2,
},
{
map[string]string{
"b": "B attribute",
"d": "D attribute",
"f": "F attribute",
},
"a: b\nc: d\ne: f",
0,
},
}
for _, d := range data {
count := checkCPUAttribs(d.cpuinfo, d.required)
assert.Equal(d.expectCount, count, "%+v", d)
}
}
func TestCheckHaveKernelModule(t *testing.T) {
if tc.NotValid(ktu.NeedRoot()) {
t.Skip(testDisabledAsNonRoot)
}
assert := assert.New(t)
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
savedModProbeCmd := modProbeCmd
savedSysModuleDir := sysModuleDir
// XXX: override (fake the modprobe command failing)
modProbeCmd = "false"
sysModuleDir = filepath.Join(dir, "sys/module")
defer func() {
modProbeCmd = savedModProbeCmd
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
modProbeCmd = "true"
result = haveKernelModule(module)
assert.True(result)
// disable "modprobe" again
modProbeCmd = "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)
savedModProbeCmd := modProbeCmd
savedSysModuleDir := sysModuleDir
// XXX: override (fake the modprobe command failing)
modProbeCmd = "false"
sysModuleDir = filepath.Join(dir, "sys/module")
defer func() {
modProbeCmd = savedModProbeCmd
sysModuleDir = savedSysModuleDir
}()
err = os.MkdirAll(sysModuleDir, testDirMode)
if err != nil {
t.Fatal(err)
}
testData := map[string]kernelModule{
"foo": {
desc: "desc",
parameters: map[string]string{},
required: true,
},
"bar": {
desc: "desc",
parameters: map[string]string{
"param1": "hello",
"param2": "world",
"param3": "a",
"param4": ".",
},
required: true,
},
}
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 tc.NotValid(ktu.NeedNonRoot()) {
t.Skip(ktu.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",
},
required: true,
},
}
savedModProbeCmd := modProbeCmd
savedSysModuleDir := sysModuleDir
// XXX: override (fake the modprobe command failing)
modProbeCmd = "false"
sysModuleDir = filepath.Join(dir, "sys/module")
defer func() {
modProbeCmd = savedModProbeCmd
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",
},
required: true,
},
}
savedModProbeCmd := modProbeCmd
savedSysModuleDir := sysModuleDir
// XXX: override (fake the modprobe command failing)
modProbeCmd = "false"
sysModuleDir = filepath.Join(dir, "sys/module")
defer func() {
modProbeCmd = savedModProbeCmd
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)
_, config, err := makeRuntimeConfig(dir)
assert.NoError(err)
oldProcCPUInfo := procCPUInfo
// doesn't exist
procCPUInfo = filepath.Join(dir, "cpuinfo")
defer func() {
procCPUInfo = oldProcCPUInfo
}()
flagSet := &flag.FlagSet{}
ctx := createCLIContext(flagSet)
ctx.App.Name = "foo"
ctx.App.Metadata["runtimeConfig"] = config
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)
savedModProbeCmd := modProbeCmd
savedSysModuleDir := sysModuleDir
// XXX: override (fake the modprobe command failing)
modProbeCmd = "false"
sysModuleDir = filepath.Join(dir, "sys/module")
defer func() {
modProbeCmd = savedModProbeCmd
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{},
required: true,
},
"bar": {
desc: "desc",
parameters: map[string]string{
"param1": "hello",
"param2": "world",
},
required: true,
},
}
checkKernelParamHandler(assert, testData1, testData1, handler, false, uint32(0))
testDataToCreate := map[string]kernelModule{
"foo": {
desc: "desc",
parameters: map[string]string{
"param1": "moo",
},
required: true,
},
}
testDataToExpect := map[string]kernelModule{
"foo": {
desc: "desc",
parameters: map[string]string{
"param1": "bar",
},
required: true,
},
}
// 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))
}
func TestArchRequiredKernelModules(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)
err = setCPUtype(config.HypervisorType)
assert.NoError(err)
if len(archRequiredKernelModules) == 0 {
// No modules to check
return
}
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
savedModProbeCmd := modProbeCmd
savedSysModuleDir := sysModuleDir
// XXX: override
sysModuleDir = filepath.Join(dir, "sys/module")
modProbeCmd = "false"
defer func() {
sysModuleDir = savedSysModuleDir
modProbeCmd = savedModProbeCmd
}()
// Running check with no modules
count, err := checkKernelModules(archRequiredKernelModules, nil)
assert.NoError(err)
// Test that count returned matches the # of modules with required set.
expectedCount := 0
for _, module := range archRequiredKernelModules {
if module.required {
expectedCount++
}
}
assert.EqualValues(count, expectedCount)
}

View File

@@ -0,0 +1,452 @@
// Copyright (c) 2017-2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"encoding/json"
"errors"
"os"
"runtime"
"strings"
"github.com/BurntSushi/toml"
"github.com/kata-containers/kata-containers/src/runtime/pkg/utils"
vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers"
exp "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/experimental"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/oci"
vcUtils "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/utils"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/prometheus/procfs"
"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.25"
// 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
}
// InitrdInfo stores initrd image details
type InitrdInfo struct {
Path string
}
// ImageInfo stores root filesystem image details
type ImageInfo struct {
Path string
}
// CPUInfo stores host CPU details
type CPUInfo struct {
Vendor string
Model string
CPUs int
}
// MemoryInfo stores host memory details
type MemoryInfo struct {
Total uint64
Free uint64
Available uint64
}
// RuntimeConfigInfo stores runtime config details.
type RuntimeConfigInfo struct {
Path string
}
// RuntimeInfo stores runtime details.
type RuntimeInfo struct {
Config RuntimeConfigInfo
Path string
Experimental []exp.Feature
Version RuntimeVersionInfo
Debug bool
Trace bool
DisableGuestSeccomp bool
DisableNewNetNs bool
SandboxCgroupOnly bool
}
type VersionInfo struct {
Semver string
Commit string
Major uint64
Minor uint64
Patch uint64
}
// RuntimeVersionInfo stores details of the runtime version
type RuntimeVersionInfo struct {
OCI string
Version VersionInfo
}
// HypervisorInfo stores hypervisor details
type HypervisorInfo struct {
MachineType string
Version string
Path string
BlockDeviceDriver string
EntropySource string
SharedFS string
VirtioFSDaemon string
Msize9p uint32
MemorySlots uint32
PCIeRootPort uint32
HotplugVFIOOnRootBus bool
Debug bool
}
// AgentInfo stores agent details
type AgentInfo struct {
TraceMode string
TraceType string
Debug bool
Trace bool
}
// 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
Memory MemoryInfo
VMContainerCapable bool
SupportVSocks bool
}
// NetmonInfo stores netmon details
type NetmonInfo struct {
Path string
Version VersionInfo
Debug bool
Enable 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 {
Kernel KernelInfo
Meta MetaInfo
Image ImageInfo
Initrd InitrdInfo
Agent AgentInfo
Hypervisor HypervisorInfo
Netmon NetmonInfo
Runtime RuntimeInfo
Host HostInfo
}
func getMetaInfo() MetaInfo {
return MetaInfo{
Version: formatVersion,
}
}
func getRuntimeInfo(configFile string, config oci.RuntimeConfig) RuntimeInfo {
runtimeVersionInfo := constructVersionInfo(version)
runtimeVersionInfo.Commit = commit
runtimeVersion := RuntimeVersionInfo{
Version: runtimeVersionInfo,
OCI: specs.Version,
}
runtimeConfig := RuntimeConfigInfo{
Path: configFile,
}
runtimePath, _ := os.Executable()
return RuntimeInfo{
Debug: config.Debug,
Trace: config.Trace,
Version: runtimeVersion,
Config: runtimeConfig,
Path: runtimePath,
DisableNewNetNs: config.DisableNewNetNs,
SandboxCgroupOnly: config.SandboxCgroupOnly,
Experimental: config.Experimental,
DisableGuestSeccomp: config.DisableGuestSeccomp,
}
}
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,
CPUs: runtime.NumCPU(),
}
supportVSocks, _ := vcUtils.SupportsVsocks()
memoryInfo := getMemoryInfo()
host := HostInfo{
Kernel: hostKernelVersion,
Architecture: arch,
Distro: hostDistro,
CPU: hostCPU,
Memory: memoryInfo,
VMContainerCapable: hostVMContainerCapable,
SupportVSocks: supportVSocks,
}
return host, nil
}
func getMemoryInfo() MemoryInfo {
fs, err := procfs.NewDefaultFS()
if err != nil {
return MemoryInfo{}
}
mi, err := fs.Meminfo()
if err != nil {
return MemoryInfo{}
}
return MemoryInfo{
Total: *mi.MemTotal,
Free: *mi.MemFree,
Available: *mi.MemAvailable,
}
}
func getNetmonInfo(config oci.RuntimeConfig) NetmonInfo {
netmonConfig := config.NetmonConfig
var netmonVersionInfo VersionInfo
if version, err := getCommandVersion(netmonConfig.Path); err != nil {
netmonVersionInfo = unknownVersionInfo
} else {
netmonVersionInfo = constructVersionInfo(version)
}
netmon := NetmonInfo{
Version: netmonVersionInfo,
Path: netmonConfig.Path,
Debug: netmonConfig.Debug,
Enable: netmonConfig.Enable,
}
return netmon
}
func getCommandVersion(cmd string) (string, error) {
return utils.RunCommand([]string{cmd, "--version"})
}
func getAgentInfo(config oci.RuntimeConfig) (AgentInfo, error) {
agent := AgentInfo{}
agentConfig := config.AgentConfig
agent.Debug = agentConfig.Debug
agent.Trace = agentConfig.Trace
agent.TraceMode = agentConfig.TraceMode
agent.TraceType = agentConfig.TraceType
return agent, nil
}
func getHypervisorInfo(config oci.RuntimeConfig) HypervisorInfo {
hypervisorPath := config.HypervisorConfig.HypervisorPath
version, err := getCommandVersion(hypervisorPath)
if err != nil {
version = unknown
}
return HypervisorInfo{
Debug: config.HypervisorConfig.Debug,
MachineType: config.HypervisorConfig.HypervisorMachineType,
Version: version,
Path: hypervisorPath,
BlockDeviceDriver: config.HypervisorConfig.BlockDeviceDriver,
Msize9p: config.HypervisorConfig.Msize9p,
MemorySlots: config.HypervisorConfig.MemSlots,
EntropySource: config.HypervisorConfig.EntropySource,
SharedFS: config.HypervisorConfig.SharedFS,
VirtioFSDaemon: config.HypervisorConfig.VirtioFSDaemon,
HotplugVFIOOnRootBus: config.HypervisorConfig.HotplugVFIOOnRootBus,
PCIeRootPort: config.HypervisorConfig.PCIeRootPort,
}
}
func getEnvInfo(configFile string, config oci.RuntimeConfig) (env EnvInfo, err error) {
err = setCPUtype(config.HypervisorType)
if err != nil {
return EnvInfo{}, err
}
meta := getMetaInfo()
runtime := getRuntimeInfo(configFile, config)
host, err := getHostInfo()
if err != nil {
return EnvInfo{}, err
}
netmon := getNetmonInfo(config)
agent, err := getAgentInfo(config)
if err != nil {
return EnvInfo{}, err
}
hypervisor := getHypervisorInfo(config)
image := ImageInfo{
Path: config.HypervisorConfig.ImagePath,
}
kernel := KernelInfo{
Path: config.HypervisorConfig.KernelPath,
Parameters: strings.Join(vc.SerializeParams(config.HypervisorConfig.KernelParams, "="), " "),
}
initrd := InitrdInfo{
Path: config.HypervisorConfig.InitrdPath,
}
env = EnvInfo{
Meta: meta,
Runtime: runtime,
Hypervisor: hypervisor,
Image: image,
Kernel: kernel,
Initrd: initrd,
Agent: agent,
Host: host,
Netmon: netmon,
}
return env, nil
}
func handleSettings(file *os.File, c *cli.Context) error {
if file == nil {
return errors.New("Invalid output file specified")
}
configFile, ok := c.App.Metadata["configFile"].(string)
if !ok {
return errors.New("cannot determine config file")
}
runtimeConfig, ok := c.App.Metadata["runtimeConfig"].(oci.RuntimeConfig)
if !ok {
return errors.New("cannot determine runtime config")
}
env, err := getEnvInfo(configFile, runtimeConfig)
if err != nil {
return err
}
if c.Bool("json") {
return writeJSONSettings(env, file)
}
return writeTOMLSettings(env, file)
}
func writeTOMLSettings(env EnvInfo, file *os.File) error {
encoder := toml.NewEncoder(file)
err := encoder.Encode(env)
if err != nil {
return err
}
return nil
}
func writeJSONSettings(env EnvInfo, file *os.File) error {
encoder := json.NewEncoder(file)
// Make it more human readable
encoder.SetIndent("", " ")
err := encoder.Encode(env)
if err != nil {
return err
}
return nil
}
var kataEnvCLICommand = cli.Command{
Name: "env",
Aliases: []string{"kata-env"},
Usage: "display settings. Default to TOML",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "json",
Usage: "Format output as JSON",
},
},
Action: func(context *cli.Context) error {
return handleSettings(defaultOutputFile, context)
},
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func getExpectedHostDetails(tmpdir string) (HostInfo, error) {
expectedVendor := "moi"
expectedModel := "awesome XI"
expectedVMContainerCapable := false
return genericGetExpectedHostDetails(tmpdir, expectedVendor, expectedModel, expectedVMContainerCapable)
}
func TestEnvGetEnvInfoSetsCPUType(t *testing.T) {
assert := assert.New(t)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
savedArchRequiredCPUFlags := archRequiredCPUFlags
savedArchRequiredCPUAttribs := archRequiredCPUAttribs
savedArchRequiredKernelModules := archRequiredKernelModules
defer func() {
archRequiredCPUFlags = savedArchRequiredCPUFlags
archRequiredCPUAttribs = savedArchRequiredCPUAttribs
archRequiredKernelModules = savedArchRequiredKernelModules
}()
archRequiredCPUFlags = map[string]string{}
archRequiredCPUAttribs = map[string]string{}
archRequiredKernelModules = map[string]kernelModule{}
configFile, config, err := makeRuntimeConfig(tmpdir)
assert.NoError(err)
expectedEnv, err := getExpectedSettings(config, tmpdir, configFile)
assert.NoError(err)
env, err := getEnvInfo(configFile, config)
assert.NoError(err)
// Free/Available are changing
expectedEnv.Host.Memory = env.Host.Memory
assert.Equal(expectedEnv, env)
assert.NotEmpty(archRequiredCPUFlags)
assert.NotEmpty(archRequiredCPUAttribs)
assert.NotEmpty(archRequiredKernelModules)
assert.Equal(archRequiredCPUFlags["vmx"], "Virtualization support")
_, ok := archRequiredKernelModules["kvm"]
assert.True(ok)
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) 2018 ARM Limited
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"testing"
)
func getExpectedHostDetails(tmpdir string) (HostInfo, error) {
expectedVendor := "0x41"
expectedModel := "8"
expectedVMContainerCapable := true
return genericGetExpectedHostDetails(tmpdir, expectedVendor, expectedModel, expectedVMContainerCapable)
}
func TestEnvGetEnvInfoSetsCPUType(t *testing.T) {
testEnvGetEnvInfoSetsCPUTypeGeneric(t)
}

View File

@@ -0,0 +1,56 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
// +build arm64 ppc64le
package main
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func testEnvGetEnvInfoSetsCPUTypeGeneric(t *testing.T) {
assert := assert.New(t)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
savedArchRequiredCPUFlags := archRequiredCPUFlags
savedArchRequiredCPUAttribs := archRequiredCPUAttribs
savedArchRequiredKernelModules := archRequiredKernelModules
defer func() {
archRequiredCPUFlags = savedArchRequiredCPUFlags
archRequiredCPUAttribs = savedArchRequiredCPUAttribs
archRequiredKernelModules = savedArchRequiredKernelModules
}()
assert.Empty(archRequiredCPUFlags)
assert.Empty(archRequiredCPUAttribs)
assert.NotEmpty(archRequiredKernelModules)
configFile, config, err := makeRuntimeConfig(tmpdir)
assert.NoError(err)
expectedEnv, err := getExpectedSettings(config, tmpdir, configFile)
assert.NoError(err)
env, err := getEnvInfo(configFile, config)
assert.NoError(err)
// Free/Available are changing
expectedEnv.Host.Memory = env.Host.Memory
assert.Equal(expectedEnv, env)
assert.Equal(archRequiredCPUFlags, savedArchRequiredCPUFlags)
assert.Equal(archRequiredCPUAttribs, savedArchRequiredCPUAttribs)
assert.Equal(archRequiredKernelModules, savedArchRequiredKernelModules)
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) 2018 IBM
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import "testing"
func getExpectedHostDetails(tmpdir string) (HostInfo, error) {
expectedVendor := ""
expectedModel := "POWER8"
expectedVMContainerCapable := true
return genericGetExpectedHostDetails(tmpdir, expectedVendor, expectedModel, expectedVMContainerCapable)
}
func TestEnvGetEnvInfoSetsCPUType(t *testing.T) {
testEnvGetEnvInfoSetsCPUTypeGeneric(t)
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) 2021 IBM
//
// SPDX-License-Identifier: Apache-2.0
//
package main
func getExpectedHostDetails(tmpdir string) (HostInfo, error) {
expectedVendor := "moi"
expectedModel := "awesome XI"
expectedVMContainerCapable := true
return genericGetExpectedHostDetails(tmpdir, expectedVendor, expectedModel, expectedVMContainerCapable)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,208 @@
// Copyright (c) 2017-2019 Intel Corporation
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"strings"
"sync"
"time"
"github.com/containerd/console"
kataMonitor "github.com/kata-containers/kata-containers/src/runtime/pkg/kata-monitor"
"github.com/kata-containers/kata-containers/src/runtime/pkg/katautils"
clientUtils "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/agent/protocols/client"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
const (
// The buffer size used to specify the buffer for IO streams copy
bufSize = 1024 * 2
defaultTimeout = 3 * time.Second
subCommandName = "exec"
// command-line parameters name
paramDebugConsolePort = "kata-debug-port"
defaultKernelParamDebugConsoleVPortValue = 1026
)
var (
bufPool = sync.Pool{
New: func() interface{} {
buffer := make([]byte, bufSize)
return &buffer
},
}
)
var kataExecCLICommand = cli.Command{
Name: subCommandName,
Usage: "Enter into guest by debug console",
Flags: []cli.Flag{
cli.Uint64Flag{
Name: paramDebugConsolePort,
Usage: "Port that debug console is listening on. (Default: 1026)",
},
},
Action: func(context *cli.Context) error {
port := context.Uint64(paramDebugConsolePort)
if port == 0 {
port = defaultKernelParamDebugConsoleVPortValue
}
sandboxID := context.Args().Get(0)
if err := katautils.VerifyContainerID(sandboxID); err != nil {
return err
}
conn, err := getConn(sandboxID, port)
if err != nil {
return err
}
defer conn.Close()
con := console.Current()
defer con.Reset()
if err := con.SetRaw(); err != nil {
return err
}
iostream := &iostream{
conn: conn,
exitch: make(chan struct{}),
closed: false,
}
ioCopy(iostream, con)
<-iostream.exitch
return nil
},
}
func ioCopy(stream *iostream, con console.Console) {
var wg sync.WaitGroup
// stdin
go func() {
p := bufPool.Get().(*[]byte)
defer bufPool.Put(p)
io.CopyBuffer(stream, con, *p)
}()
// stdout
wg.Add(1)
go func() {
p := bufPool.Get().(*[]byte)
defer bufPool.Put(p)
io.CopyBuffer(os.Stdout, stream, *p)
wg.Done()
}()
wg.Wait()
close(stream.exitch)
}
type iostream struct {
conn net.Conn
exitch chan struct{}
closed bool
}
func (s *iostream) Write(data []byte) (n int, err error) {
if s.closed {
return 0, errors.New("stream closed")
}
return s.conn.Write(data)
}
func (s *iostream) Close() error {
if s.closed {
return errors.New("stream closed")
}
err := s.conn.Close()
if err == nil {
s.closed = true
}
return err
}
func (s *iostream) Read(data []byte) (n int, err error) {
if s.closed {
return 0, errors.New("stream closed")
}
return s.conn.Read(data)
}
func getConn(sandboxID string, port uint64) (net.Conn, error) {
client, err := kataMonitor.BuildShimClient(sandboxID, defaultTimeout)
if err != nil {
return nil, err
}
resp, err := client.Get("http://shim/agent-url")
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Failure from %s shim-monitor: %d", sandboxID, resp.StatusCode)
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
sock := strings.TrimSuffix(string(data), "\n")
addr, err := url.Parse(sock)
if err != nil {
return nil, err
}
// validate more
switch addr.Scheme {
case clientUtils.VSockSocketScheme:
// vsock://31513974:1024
cidAndPort := strings.Split(addr.Host, ":")
if len(cidAndPort) != 2 {
return nil, fmt.Errorf("Invalid vsock scheme: %s", sock)
}
shimAddr := fmt.Sprintf("%s:%s:%d", clientUtils.VSockSocketScheme, cidAndPort[0], port)
return clientUtils.VsockDialer(shimAddr, defaultTimeout)
case clientUtils.HybridVSockScheme:
// addr: hvsock:///run/vc/firecracker/340b412c97bf1375cdda56bfa8f18c8a/root/kata.hvsock:1024
hvsocket := strings.Split(addr.Path, ":")
if len(hvsocket) != 2 {
return nil, fmt.Errorf("Invalid hybrid vsock scheme: %s", sock)
}
// hvsock:///run/vc/firecracker/340b412c97bf1375cdda56bfa8f18c8a/root/kata.hvsock
shimAddr := fmt.Sprintf("%s:%s:%d", clientUtils.HybridVSockScheme, hvsocket[0], port)
return clientUtils.HybridVSockDialer(shimAddr, defaultTimeout)
}
return nil, fmt.Errorf("schema %s not found", addr.Scheme)
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2021 Apple Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
kataMonitor "github.com/kata-containers/kata-containers/src/runtime/pkg/kata-monitor"
"github.com/kata-containers/kata-containers/src/runtime/pkg/katautils"
"github.com/urfave/cli"
)
var kataMetricsCLICommand = cli.Command{
Name: "metrics",
Usage: "gather metrics associated with infrastructure used to run a sandbox",
UsageText: "metrics <sandbox id>",
Action: func(context *cli.Context) error {
sandboxID := context.Args().Get(0)
if err := katautils.VerifyContainerID(sandboxID); err != nil {
return err
}
// Get the metrics!
metrics, err := kataMonitor.GetSandboxMetrics(sandboxID)
if err != nil {
return err
}
fmt.Printf("%s\n", metrics)
return nil
},
}

View File

@@ -0,0 +1,498 @@
// Copyright (c) 2014,2015,2016 Docker, Inc.
// Copyright (c) 2017-2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"context"
"errors"
"fmt"
"io"
"os"
"os/signal"
goruntime "runtime"
"strings"
"syscall"
"github.com/kata-containers/kata-containers/src/runtime/pkg/katautils"
"github.com/kata-containers/kata-containers/src/runtime/pkg/signals"
vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers"
exp "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/experimental"
vf "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/factory"
tl "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/factory/template"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/oci"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/rootless"
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. We set the default to 'Warn' for the runtime.
var originalLoggerLevel = logrus.WarnLevel
var debug = 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: "config, kata-config",
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.StringFlag{
Name: "rootless",
Value: "auto",
Usage: "ignore cgroup permission errors ('true', 'false', or 'auto')",
},
cli.BoolFlag{
Name: "show-default-config-paths, kata-show-default-config-paths",
Usage: "show config file paths that will be checked for (in order)",
},
cli.BoolFlag{
Name: "systemd-cgroup",
Usage: "enable systemd cgroup support, expects cgroupsPath to be of form \"slice:prefix:name\" for e.g. \"system.slice:runc:434234\"",
},
}
// runtimeCommands is the list of supported command-line (sub-)
// commands.
var runtimeCommands = []cli.Command{
versionCLICommand,
// Kata Containers specific extensions
kataCheckCLICommand,
kataEnvCLICommand,
kataExecCLICommand,
kataMetricsCLICommand,
factoryCLICommand,
}
// 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",
"arch": arch,
"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
}
// setupSignalHandler sets up signal handling, starting a go routine to deal
// with signals as they arrive.
func setupSignalHandler(ctx context.Context) {
signals.SetLogger(kataLog)
sigCh := make(chan os.Signal, 8)
for _, sig := range signals.HandledSignals() {
signal.Notify(sigCh, sig)
}
go func() {
for {
sig := <-sigCh
nativeSignal, ok := sig.(syscall.Signal)
if !ok {
err := errors.New("unknown signal")
kataLog.WithError(err).WithField("signal", sig.String()).Error()
continue
}
if signals.FatalSignal(nativeSignal) {
kataLog.WithField("signal", sig).Error("received fatal signal")
} else if debug && signals.NonFatalSignal(nativeSignal) {
kataLog.WithField("signal", sig).Debug("handling signal")
signals.Backtrace()
}
}
}()
}
// setExternalLoggers registers the specified logger with the external
// packages which accept a logger to handle their own logging.
func setExternalLoggers(ctx context.Context, logger *logrus.Entry) {
// Set virtcontainers logger.
vci.SetLogger(ctx, logger)
// Set vm factory logger.
vf.SetLogger(ctx, logger)
// Set vm factory template logger.
tl.SetLogger(ctx, logger)
// Set the OCI package logger.
oci.SetLogger(ctx, logger)
// Set the katautils package logger
katautils.SetLogger(ctx, logger, originalLoggerLevel)
// Set the rootless package logger
rootless.SetLogger(ctx, logger)
}
// beforeSubcommands is the function to perform preliminary checks
// before command-line parsing occurs.
func beforeSubcommands(c *cli.Context) error {
var configFile string
var runtimeConfig oci.RuntimeConfig
var err error
katautils.SetConfigOptions(name, defaultRuntimeConfiguration, defaultSysConfRuntimeConfiguration)
handleShowConfig(c)
if userWantsUsage(c) {
// No setup required if the user just
// wants to see the usage statement.
return nil
}
r, err := parseBoolOrAuto(c.GlobalString("rootless"))
if err != nil {
return err
}
// If flag is true/false, assign the rootless flag.
// vc will not perform any auto-detection in that case.
// In case flag is nil or auto, vc detects if the runtime is running as rootless.
if r != nil {
rootless.SetRootless(*r)
}
// Support --systed-cgroup
// Issue: https://github.com/kata-containers/runtime/issues/2428
ignoreConfigLogs := false
subCmdIsCheckCmd := (c.NArg() >= 1 && ((c.Args()[0] == "kata-check") || (c.Args()[0] == "check")))
if subCmdIsCheckCmd {
// checkCmd will use the default logrus logger to stderr
// raise the logger default level to warn
kataLog.Logger.SetLevel(logrus.WarnLevel)
// do not print configuration logs for checkCmd
ignoreConfigLogs = true
} else {
if path := c.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 c.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", c.GlobalString("log-format"))
}
// Add the name of the sub-command to each log entry for easier
// debugging.
cmdName := c.Args().First()
if c.App.Command(cmdName) != nil {
kataLog = kataLog.WithField("command", cmdName)
}
ctx, err := cliContextToContext(c)
if err != nil {
return err
}
setExternalLoggers(ctx, kataLog)
if c.NArg() == 1 && (c.Args()[0] == "kata-env" || c.Args()[0] == "env") {
// simply report the logging setup
ignoreConfigLogs = true
}
}
configFile, runtimeConfig, err = katautils.LoadConfiguration(c.GlobalString("kata-config"), ignoreConfigLogs)
if err != nil {
fatal(err)
}
if !subCmdIsCheckCmd {
debug = runtimeConfig.Debug
}
args := strings.Join(c.Args(), " ")
fields := logrus.Fields{
"version": version,
"commit": commit,
"arguments": `"` + args + `"`,
}
err = addExpFeatures(c, runtimeConfig)
if err != nil {
return err
}
kataLog.WithFields(fields).Info()
// make the data accessible to the sub-commands.
c.App.Metadata["runtimeConfig"] = runtimeConfig
c.App.Metadata["configFile"] = configFile
return nil
}
// handleShowConfig determines if the user wishes to see the configuration
// paths. If so, it will display them and then exit.
func handleShowConfig(context *cli.Context) {
if context.GlobalBool("show-default-config-paths") {
files := katautils.GetDefaultConfigFilePaths()
for _, file := range files {
fmt.Fprintf(defaultOutputFile, "%s\n", file)
}
exit(0)
}
}
// add supported experimental features in context
func addExpFeatures(clictx *cli.Context, runtimeConfig oci.RuntimeConfig) error {
ctx, err := cliContextToContext(clictx)
if err != nil {
return err
}
var exps []string
for _, e := range runtimeConfig.Experimental {
exps = append(exps, e.Name)
}
ctx = exp.ContextWithExp(ctx, exps)
// Add experimental features to metadata and update the context
clictx.App.Metadata["context"] = ctx
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(ctx context.Context, 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
// allow sub-commands to access context
app.Metadata = map[string]interface{}{
"context": ctx,
}
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(ctx context.Context) {
setupSignalHandler(ctx)
setCLIGlobals()
err := createRuntimeApp(ctx, os.Args)
if err != nil {
fatal(err)
}
}
// cliContextToContext extracts the generic context from the specified
// cli context.
func cliContextToContext(c *cli.Context) (context.Context, error) {
if c == nil {
return nil, errors.New("need cli.Context")
}
// extract the main context
ctx, ok := c.App.Metadata["context"].(context.Context)
if !ok {
return nil, errors.New("invalid or missing context in metadata")
}
return ctx, nil
}
func main() {
// create a new empty context
ctx := context.Background()
createRuntime(ctx)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,418 @@
// Copyright (c) 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/blang/semver"
)
type ReleaseCmd int
type releaseDetails struct {
date string
url string
filename string
version semver.Version
}
const (
// A release URL is expected to be prefixed with this value
projectAPIURL = "https://api.github.com/repos/" + projectORG
releasesSuffix = "/releases"
downloadsSuffix = releasesSuffix + "/download"
// Kata 1.x
kata1xRepo = "runtime"
kataLegacyReleaseURL = projectAPIURL + "/" + kata1xRepo + releasesSuffix
kataLegacyDownloadURL = projectURL + "/" + kata1xRepo + downloadsSuffix
// Kata 2.x or newer
kata2xRepo = "kata-containers"
kataReleaseURL = projectAPIURL + "/" + kata2xRepo + releasesSuffix
kataDownloadURL = projectURL + "/" + kata2xRepo + downloadsSuffix
// Environment variable that can be used to override a release URL
ReleaseURLEnvVar = "KATA_RELEASE_URL"
RelCmdList ReleaseCmd = iota
RelCmdCheck ReleaseCmd = iota
msgNoReleases = "No releases available"
msgNoNewerRelease = "No newer release available"
errNoNetChecksAsRoot = "No network checks allowed running as super user"
)
func (c ReleaseCmd) Valid() bool {
switch c {
case RelCmdCheck, RelCmdList:
return true
default:
return false
}
}
func downloadURLIsValid(url string) error {
if url == "" {
return errors.New("URL cannot be blank")
}
if strings.HasPrefix(url, kataDownloadURL) ||
strings.HasPrefix(url, kataLegacyDownloadURL) {
return nil
}
return fmt.Errorf("Download URL %q is not valid", url)
}
func releaseURLIsValid(url string) error {
if url == "" {
return errors.New("URL cannot be blank")
}
if url == kataReleaseURL || url == kataLegacyReleaseURL {
return nil
}
return fmt.Errorf("Release URL %q is not valid", url)
}
func getReleaseURL(currentVersion semver.Version) (url string, err error) {
major := currentVersion.Major
if major == 0 {
return "", fmt.Errorf("invalid current version: %v", currentVersion)
} else if major == 1 {
url = kataLegacyReleaseURL
} else {
url = kataReleaseURL
}
if value := os.Getenv(ReleaseURLEnvVar); value != "" {
url = value
}
if err := releaseURLIsValid(url); err != nil {
return "", err
}
return url, nil
}
func ignoreRelease(release releaseDetails, includeAll bool) bool {
if includeAll {
return false
}
if len(release.version.Pre) > 0 {
// Pre-releases are ignored by default
return true
}
return false
}
// Returns a release version and release object from the specified map.
func makeRelease(release map[string]interface{}) (version string, details releaseDetails, err error) {
key := "tag_name"
version, ok := release[key].(string)
if !ok {
return "", details, fmt.Errorf("failed to find key %s in release data", key)
}
if version == "" {
return "", details, fmt.Errorf("release version cannot be blank")
}
releaseSemver, err := semver.Make(version)
if err != nil {
return "", details, fmt.Errorf("release %q has invalid semver version: %v", version, err)
}
key = "assets"
assetsArray, ok := release[key].([]interface{})
if !ok {
return "", details, fmt.Errorf("failed to find key %s in release version %q data", key, version)
}
if len(assetsArray) == 0 {
// GitHub auto-creates the source assets, but binaries have to
// be built and uploaded for a release.
return "", details, fmt.Errorf("no binary assets for release %q", version)
}
var createDate string
var filename string
var downloadURL string
assets := assetsArray[0]
key = "browser_download_url"
downloadURL, ok = assets.(map[string]interface{})[key].(string)
if !ok {
return "", details, fmt.Errorf("failed to find key %s in release version %q asset data", key, version)
}
if err := downloadURLIsValid(downloadURL); err != nil {
return "", details, err
}
key = "name"
filename, ok = assets.(map[string]interface{})[key].(string)
if !ok {
return "", details, fmt.Errorf("failed to find key %s in release version %q asset data", key, version)
}
if filename == "" {
return "", details, fmt.Errorf("Release %q asset missing filename", version)
}
key = "created_at"
createDate, ok = assets.(map[string]interface{})[key].(string)
if !ok {
return "", details, fmt.Errorf("failed to find key %s in release version %q asset data", key, version)
}
if createDate == "" {
return "", details, fmt.Errorf("Release %q asset missing creation date", version)
}
details = releaseDetails{
version: releaseSemver,
date: createDate,
url: downloadURL,
filename: filename,
}
return version, details, nil
}
func readReleases(releasesArray []map[string]interface{}, includeAll bool) (versions []semver.Version,
releases map[string]releaseDetails) {
releases = make(map[string]releaseDetails)
for _, release := range releasesArray {
version, details, err := makeRelease(release)
// Don't error if makeRelease() fails to construct a release.
// There are many reasons a release may not be considered
// valid, so just ignore the invalid ones.
if err != nil {
kataLog.WithField("version", version).WithError(err).Debug("ignoring invalid release version")
continue
}
if ignoreRelease(details, includeAll) {
continue
}
versions = append(versions, details.version)
releases[version] = details
}
semver.Sort(versions)
return versions, releases
}
// Note: Assumes versions is sorted in ascending order
func findNewestRelease(currentVersion semver.Version, versions []semver.Version) (bool, semver.Version, error) {
var candidates []semver.Version
if len(versions) == 0 {
return false, semver.Version{}, errors.New("no versions available")
}
for _, version := range versions {
if currentVersion.GTE(version) {
// Ignore older releases (and the current one!)
continue
}
candidates = append(candidates, version)
}
count := len(candidates)
if count == 0 {
return false, semver.Version{}, nil
}
return true, candidates[count-1], nil
}
func getReleases(releaseURL string, includeAll bool) ([]semver.Version, map[string]releaseDetails, error) {
kataLog.WithField("url", releaseURL).Info("Looking for releases")
if os.Geteuid() == 0 {
return nil, nil, errors.New(errNoNetChecksAsRoot)
}
client := &http.Client{}
resp, err := client.Get(releaseURL)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
releasesArray := []map[string]interface{}{}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, nil, fmt.Errorf("failed to read release details: %v", err)
} else if resp.StatusCode == http.StatusForbidden && bytes.Contains(body, []byte("limit exceeded")) {
// Do not fail if rate limit is exceeded
kataLog.WithField("url", releaseURL).
Warn("API rate limit exceeded. Try again later. Read https://docs.github.com/apps/building-github-apps/understanding-rate-limits-for-github-apps for more information")
return []semver.Version{}, map[string]releaseDetails{}, nil
}
if err := json.Unmarshal(body, &releasesArray); err != nil {
return nil, nil, fmt.Errorf("failed to unpack release details: %v", err)
}
versions, releases := readReleases(releasesArray, includeAll)
return versions, releases, nil
}
func getNewReleaseType(current semver.Version, latest semver.Version) (string, error) {
if current.GT(latest) {
return "", fmt.Errorf("current version %s newer than latest %s", current, latest)
}
if current.EQ(latest) {
return "", fmt.Errorf("current version %s and latest are same", current)
}
var desc string
if latest.Major > current.Major {
if len(latest.Pre) > 0 {
desc = "major pre-release"
} else {
desc = "major"
}
} else if latest.Minor > current.Minor {
if len(latest.Pre) > 0 {
desc = "minor pre-release"
} else {
desc = "minor"
}
} else if latest.Patch > current.Patch {
if len(latest.Pre) > 0 {
desc = "patch pre-release"
} else {
desc = "patch"
}
} else if latest.Patch == current.Patch && len(latest.Pre) > 0 {
desc = "pre-release"
} else if latest.Major == current.Major &&
latest.Minor == current.Minor &&
latest.Patch == current.Patch {
if len(current.Pre) > 0 && len(latest.Pre) == 0 {
desc = "major"
}
} else {
return "", fmt.Errorf("BUG: unhandled scenario: current version: %s, latest version: %s", current, latest)
}
return desc, nil
}
func showLatestRelease(output *os.File, current semver.Version, details releaseDetails) error {
latest := details.version
desc, err := getNewReleaseType(current, latest)
if err != nil {
return err
}
fmt.Fprintf(output, "Newer %s release available: %s (url: %v, date: %v)\n",
desc,
details.version, details.url, details.date)
return nil
}
func listReleases(output *os.File, current semver.Version, versions []semver.Version, releases map[string]releaseDetails) error {
for _, version := range versions {
details, ok := releases[version.String()]
if !ok {
return fmt.Errorf("Release %v has no details", version)
}
fmt.Fprintf(output, "%s;%s;%s\n", version, details.date, details.url)
}
return nil
}
func HandleReleaseVersions(cmd ReleaseCmd, currentVersion string, includeAll bool) error {
if !cmd.Valid() {
return fmt.Errorf("invalid release command: %v", cmd)
}
output := os.Stdout
currentSemver, err := semver.Make(currentVersion)
if err != nil {
return fmt.Errorf("BUG: Current version of %s (%s) has invalid SemVer version: %v", name, currentVersion, err)
}
releaseURL, err := getReleaseURL(currentSemver)
if err != nil {
return err
}
versions, releases, err := getReleases(releaseURL, includeAll)
if err != nil {
return err
}
if cmd == RelCmdList {
return listReleases(output, currentSemver, versions, releases)
}
if len(versions) == 0 {
fmt.Fprintf(output, "%s\n", msgNoReleases)
return nil
}
available, newest, err := findNewestRelease(currentSemver, versions)
if err != nil {
return err
}
if !available {
fmt.Fprintf(output, "%s\n", msgNoNewerRelease)
return nil
}
details, ok := releases[newest.String()]
if !ok {
return fmt.Errorf("Release %v has no details", newest)
}
return showLatestRelease(output, currentSemver, details)
}

View File

@@ -0,0 +1,519 @@
// Copyright (c) 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"os"
"strings"
"testing"
"github.com/blang/semver"
"github.com/stretchr/testify/assert"
)
var currentSemver semver.Version
var expectedReleasesURL string
func init() {
var err error
currentSemver, err = semver.Make(version)
if err != nil {
panic(fmt.Sprintf("failed to create semver for testing: %v", err))
}
if currentSemver.Major == 1 {
expectedReleasesURL = kataLegacyReleaseURL
} else {
expectedReleasesURL = kataReleaseURL
}
}
func TestReleaseCmd(t *testing.T) {
assert := assert.New(t)
for i, value := range []ReleaseCmd{RelCmdCheck, RelCmdList} {
assert.True(value.Valid(), "test[%d]: %+v", i, value)
}
for i, value := range []int{-1, 2, 42, 255} {
invalid := ReleaseCmd(i)
assert.False(invalid.Valid(), "test[%d]: %+v", i, value)
}
}
func TestGetReleaseURL(t *testing.T) {
assert := assert.New(t)
const kata1xURL = "https://api.github.com/repos/kata-containers/runtime/releases"
const kata2xURL = "https://api.github.com/repos/kata-containers/kata-containers/releases"
type testData struct {
currentVersion string
expectedURL string
expectError bool
}
data := []testData{
{"0.0.0", "", true},
{"1.0.0", kata1xURL, false},
{"1.9999.9999", kata1xURL, false},
{"2.0.0-alpha3", kata2xURL, false},
{"2.9999.9999", kata2xURL, false},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
ver, err := semver.Make(d.currentVersion)
msg = fmt.Sprintf("%s, version: %v, error: %v", msg, ver, err)
assert.NoError(err, msg)
url, err := getReleaseURL(ver)
if d.expectError {
assert.Error(err, msg)
} else {
assert.NoError(err, msg)
assert.Equal(url, d.expectedURL, msg)
assert.True(strings.HasPrefix(url, projectAPIURL), msg)
}
}
url, err := getReleaseURL(currentSemver)
assert.NoError(err)
assert.Equal(url, expectedReleasesURL)
assert.True(strings.HasPrefix(url, projectAPIURL))
}
func TestGetReleaseURLEnvVar(t *testing.T) {
assert := assert.New(t)
type testData struct {
envVarValue string
expectedURL string
expectError bool
}
data := []testData{
{"", expectedReleasesURL, false},
{"http://google.com", "", true},
{"https://katacontainers.io", "", true},
{"https://github.com/kata-containers/runtime/releases/latest", "", true},
{"https://github.com/kata-containers/kata-containers/releases/latest", "", true},
{expectedReleasesURL, expectedReleasesURL, false},
}
assert.Equal(os.Getenv("KATA_RELEASE_URL"), "")
defer os.Setenv("KATA_RELEASE_URL", "")
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
err := os.Setenv("KATA_RELEASE_URL", d.envVarValue)
msg = fmt.Sprintf("%s, error: %v", msg, err)
assert.NoError(err, msg)
url, err := getReleaseURL(currentSemver)
if d.expectError {
assert.Errorf(err, msg)
} else {
assert.NoErrorf(err, msg)
assert.Equal(d.expectedURL, url, msg)
}
}
}
func TestMakeRelease(t *testing.T) {
assert := assert.New(t)
type testData struct {
release map[string]interface{}
expectedVersion string
expectedDetails releaseDetails
expectError bool
}
invalidRel1 := map[string]interface{}{"foo": 1}
invalidRel2 := map[string]interface{}{"foo": "bar"}
invalidRel3 := map[string]interface{}{"foo": true}
testDate := "2020-09-01T22:10:44Z"
testRelVersion := "1.2.3"
testFilename := "kata-static-1.12.0-alpha1-x86_64.tar.xz"
testURL := fmt.Sprintf("https://github.com/kata-containers/runtime/releases/download/%s/%s", testRelVersion, testFilename)
testSemver, err := semver.Make(testRelVersion)
assert.NoError(err)
invalidRelMissingVersion := map[string]interface{}{}
invalidRelInvalidVersion := map[string]interface{}{
"tag_name": "not.valid.semver",
}
invalidRelMissingAssets := map[string]interface{}{
"tag_name": testRelVersion,
"name": testFilename,
"assets": []interface{}{},
}
invalidAssetsMissingURL := []interface{}{
map[string]interface{}{
"name": testFilename,
"created_at": testDate,
},
}
invalidAssetsMissingFile := []interface{}{
map[string]interface{}{
"browser_download_url": testURL,
"created_at": testDate,
},
}
invalidAssetsMissingDate := []interface{}{
map[string]interface{}{
"name": testFilename,
"browser_download_url": testURL,
},
}
validAssets := []interface{}{
map[string]interface{}{
"browser_download_url": testURL,
"name": testFilename,
"created_at": testDate,
},
}
invalidRelAssetsMissingURL := map[string]interface{}{
"tag_name": testRelVersion,
"name": testFilename,
"assets": invalidAssetsMissingURL,
}
invalidRelAssetsMissingFile := map[string]interface{}{
"tag_name": testRelVersion,
"name": testFilename,
"assets": invalidAssetsMissingFile,
}
invalidRelAssetsMissingDate := map[string]interface{}{
"tag_name": testRelVersion,
"name": testFilename,
"assets": invalidAssetsMissingDate,
}
validRel := map[string]interface{}{
"tag_name": testRelVersion,
"name": testFilename,
"assets": validAssets,
}
validReleaseDetails := releaseDetails{
version: testSemver,
date: testDate,
url: testURL,
filename: testFilename,
}
data := []testData{
{invalidRel1, "", releaseDetails{}, true},
{invalidRel2, "", releaseDetails{}, true},
{invalidRel3, "", releaseDetails{}, true},
{invalidRelMissingVersion, "", releaseDetails{}, true},
{invalidRelInvalidVersion, "", releaseDetails{}, true},
{invalidRelMissingAssets, "", releaseDetails{}, true},
{invalidRelAssetsMissingURL, "", releaseDetails{}, true},
{invalidRelAssetsMissingFile, "", releaseDetails{}, true},
{invalidRelAssetsMissingDate, "", releaseDetails{}, true},
{validRel, testRelVersion, validReleaseDetails, false},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
version, details, err := makeRelease(d.release)
msg = fmt.Sprintf("%s, version: %v, details: %+v, error: %v", msg, version, details, err)
if d.expectError {
assert.Error(err, msg)
continue
}
assert.NoError(err, msg)
assert.Equal(d.expectedVersion, version, msg)
assert.Equal(d.expectedDetails, details, msg)
}
}
func TestReleaseURLIsValid(t *testing.T) {
assert := assert.New(t)
type testData struct {
url string
expectError bool
}
data := []testData{
{"", true},
{"foo", true},
{"foo bar", true},
{"https://google.com", true},
{projectAPIURL, true},
{kataLegacyReleaseURL, false},
{kataReleaseURL, false},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
err := releaseURLIsValid(d.url)
msg = fmt.Sprintf("%s, error: %v", msg, err)
if d.expectError {
assert.Error(err, msg)
} else {
assert.NoError(err, msg)
}
}
}
func TestDownloadURLIsValid(t *testing.T) {
assert := assert.New(t)
type testData struct {
url string
expectError bool
}
validKata1xDownload := "https://github.com/kata-containers/runtime/releases/download/1.12.0-alpha1/kata-static-1.12.0-alpha1-x86_64.tar.xz"
validKata2xDownload := "https://github.com/kata-containers/kata-containers/releases/download/2.0.0-alpha3/kata-static-2.0.0-alpha3-x86_64.tar.xz"
data := []testData{
{"", true},
{"foo", true},
{"foo bar", true},
{"https://google.com", true},
{projectURL, true},
{validKata1xDownload, false},
{validKata2xDownload, false},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
err := downloadURLIsValid(d.url)
msg = fmt.Sprintf("%s, error: %v", msg, err)
if d.expectError {
assert.Error(err, msg)
} else {
assert.NoError(err, msg)
}
}
}
func TestIgnoreRelease(t *testing.T) {
assert := assert.New(t)
type testData struct {
details releaseDetails
includeAll bool
expectIgnore bool
}
verWithoutPreRelease, err := semver.Make("2.0.0")
assert.NoError(err)
verWithPreRelease, err := semver.Make("2.0.0-alpha3")
assert.NoError(err)
relWithoutPreRelease := releaseDetails{
version: verWithoutPreRelease,
}
relWithPreRelease := releaseDetails{
version: verWithPreRelease,
}
data := []testData{
{relWithoutPreRelease, false, false},
{relWithoutPreRelease, true, false},
{relWithPreRelease, false, true},
{relWithPreRelease, true, false},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
ignore := ignoreRelease(d.details, d.includeAll)
if d.expectIgnore {
assert.True(ignore, msg)
} else {
assert.False(ignore, msg)
}
}
}
func TestGetReleases(t *testing.T) {
assert := assert.New(t)
url := "foo"
expectedErrMsg := "unsupported protocol scheme"
for _, includeAll := range []bool{true, false} {
euid := os.Geteuid()
msg := fmt.Sprintf("includeAll: %v, euid: %v", includeAll, euid)
_, _, err := getReleases(url, includeAll)
msg = fmt.Sprintf("%s, error: %v", msg, err)
assert.Error(err, msg)
if euid == 0 {
assert.Equal(err.Error(), errNoNetChecksAsRoot, msg)
} else {
assert.True(strings.Contains(err.Error(), expectedErrMsg), msg)
}
}
}
func TestFindNewestRelease(t *testing.T) {
assert := assert.New(t)
type testData struct {
versions []semver.Version
currentVer semver.Version
expectVersion semver.Version
expectError bool
expectAvailable bool
}
ver1, err := semver.Make("1.11.1")
assert.NoError(err)
ver2, err := semver.Make("1.11.3")
assert.NoError(err)
ver3, err := semver.Make("2.0.0")
assert.NoError(err)
data := []testData{
{[]semver.Version{}, semver.Version{}, semver.Version{}, true, false},
{[]semver.Version{}, ver1, semver.Version{}, true, false},
{[]semver.Version{ver1}, ver1, semver.Version{}, false, false},
{[]semver.Version{ver1}, ver2, semver.Version{}, false, false},
{[]semver.Version{ver2}, ver1, ver2, false, true},
{[]semver.Version{ver3}, ver1, ver3, false, true},
{[]semver.Version{ver2, ver3}, ver1, ver3, false, true},
{[]semver.Version{ver1, ver3}, ver2, ver3, false, true},
{[]semver.Version{ver1}, ver2, semver.Version{}, false, false},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
available, version, err := findNewestRelease(d.currentVer, d.versions)
msg = fmt.Sprintf("%s, available: %v, version: %v, error: %v", msg, available, version, err)
if d.expectError {
assert.Error(err, msg)
continue
}
assert.NoError(err, msg)
if !d.expectAvailable {
assert.False(available, msg)
continue
}
assert.Equal(d.expectVersion, version)
}
}
func TestGetNewReleaseType(t *testing.T) {
assert := assert.New(t)
type testData struct {
currentVer string
latestVer string
result string
expectError bool
}
data := []testData{
// Check build metadata (ignored for version comparisons)
{"2.0.0+build", "2.0.0", "", true},
{"2.0.0+build-1", "2.0.0+build-2", "", true},
{"1.12.0+build", "1.12.0", "", true},
{"2.0.0-rc3+foo", "2.0.0", "major", false},
{"2.0.0-rc3+foo", "2.0.0-rc4", "pre-release", false},
{"1.12.0+foo", "1.13.0", "minor", false},
{"1.12.0+build", "2.0.0", "major", false},
{"1.12.0+build", "1.13.0", "minor", false},
{"1.12.0-rc2+build", "1.12.1", "patch", false},
{"1.12.0-rc2+build", "1.12.1-foo", "patch pre-release", false},
{"1.12.0-rc4+wibble", "1.12.0", "major", false},
{"2.0.0-alpha3", "1.0.0", "", true},
{"1.0.0", "1.0.0", "", true},
{"2.0.0", "1.0.0", "", true},
{"1.0.0", "2.0.0", "major", false},
{"2.0.0-alpha3", "2.0.0-alpha4", "pre-release", false},
{"1.0.0", "2.0.0-alpha3", "major pre-release", false},
{"1.0.0", "1.1.2", "minor", false},
{"1.0.0", "1.1.2-pre2", "minor pre-release", false},
{"1.0.0", "1.1.2-foo", "minor pre-release", false},
{"1.0.0", "1.0.3", "patch", false},
{"1.0.0-beta29", "1.0.0-beta30", "pre-release", false},
{"1.0.0", "1.0.3-alpha99.1b", "patch pre-release", false},
{"2.0.0-rc0", "2.0.0", "major", false},
{"2.0.0-rc1", "2.0.0", "major", false},
{"1.12.0-rc0", "1.12.0", "major", false},
{"1.12.0-rc5", "1.12.0", "major", false},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
current, err := semver.Make(d.currentVer)
msg = fmt.Sprintf("%s, current: %v, error: %v", msg, current, err)
assert.NoError(err, msg)
latest, err := semver.Make(d.latestVer)
assert.NoError(err, msg)
desc, err := getNewReleaseType(current, latest)
if d.expectError {
assert.Error(err, msg)
continue
}
assert.NoError(err, msg)
assert.Equal(d.result, desc, msg)
}
}

View File

@@ -0,0 +1,191 @@
// Copyright (c) 2014 Docker, Inc.
// Copyright (c) 2017 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/blang/semver"
"github.com/kata-containers/kata-containers/src/runtime/pkg/katautils"
)
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"
unknownVersionInfo = VersionInfo{
Semver: unknown,
Commit: unknown,
}
)
func getKernelVersion() (string, error) {
contents, err := katautils.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}
name = ""
version = ""
for _, file := range files {
contents, err := katautils.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=") && name == "" {
fields := strings.Split(line, "=")
name = strings.Trim(fields[1], `"`)
} else if strings.HasPrefix(line, "VERSION_ID=") && version == "" {
fields := strings.Split(line, "=")
version = strings.Trim(fields[1], `"`)
}
}
if name != "" && version != "" {
return name, version, nil
}
}
if name == "" {
name = unknown
}
if version == "" {
version = unknown
}
return name, version, nil
}
// genericGetCPUDetails returns the vendor and model of the CPU.
// If it is not possible to determine both values an error is
// returned.
func genericGetCPUDetails() (vendor, model string, err error) {
cpuinfo, err := getCPUInfo(procCPUInfo)
if err != nil {
return "", "", err
}
lines := strings.Split(cpuinfo, "\n")
for _, line := range lines {
if archCPUVendorField != "" {
if strings.HasPrefix(line, archCPUVendorField) {
fields := strings.Split(line, ":")
if len(fields) > 1 {
vendor = strings.TrimSpace(fields[1])
}
}
}
if archCPUModelField != "" {
if strings.HasPrefix(line, archCPUModelField) {
fields := strings.Split(line, ":")
if len(fields) > 1 {
model = strings.TrimSpace(fields[1])
}
}
}
}
if archCPUVendorField != "" && vendor == "" {
return "", "", fmt.Errorf("cannot find vendor field in file %v", procCPUInfo)
}
// model name is optional
if archCPUModelField != "" && model == "" {
return "", "", fmt.Errorf("cannot find model field in file %v", procCPUInfo)
}
return vendor, model, nil
}
// from runC
// parseBoolOrAuto returns (nil, nil) if s is empty or "auto"
func parseBoolOrAuto(s string) (*bool, error) {
if s == "" || strings.ToLower(s) == "auto" {
return nil, nil
}
b, err := strconv.ParseBool(s)
return &b, err
}
// constructVersionInfo constructs VersionInfo-type value from a version string
// in the format of "Kata-Component version Major.Minor.Patch-rc_xxx-Commit".
func constructVersionInfo(version string) VersionInfo {
fields := strings.Split(version, " ")
realVersion := fields[len(fields)-1]
sv, err := semver.Make(realVersion)
if err != nil {
return unknownVersionInfo
}
var pres string
if len(sv.Pre) > 0 {
presSplit := strings.Split(sv.Pre[0].VersionStr, "-")
if len(presSplit) > 2 {
pres = presSplit[1]
}
}
// version contains Commit info.
if len(pres) > 1 {
return VersionInfo{
Semver: realVersion,
Major: sv.Major,
Minor: sv.Minor,
Patch: sv.Patch,
Commit: pres,
}
}
return VersionInfo{
Semver: realVersion,
Major: sv.Major,
Minor: sv.Minor,
Patch: sv.Patch,
Commit: unknown,
}
}

View File

@@ -0,0 +1,221 @@
// Copyright (c) 2017 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/kata-containers/kata-containers/src/runtime/pkg/katautils"
"github.com/kata-containers/kata-containers/src/runtime/pkg/utils"
"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, katautils.FileExists(file),
fmt.Sprintf("File %q should not exist", file))
err = createEmptyFile(file)
if err != nil {
t.Fatal(err)
}
assert.True(t, katautils.FileExists(file),
fmt.Sprintf("File %q should exist", file))
}
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
}
const unknown = "<<unknown>>"
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.NoError(t, err)
data := []testData{
{"", "", unknown, unknown, false},
{"invalid", "", unknown, unknown, false},
{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 TestUtilsRunCommand(t *testing.T) {
output, err := utils.RunCommand([]string{"true"})
assert.NoError(t, err)
assert.Equal(t, "", output)
}
func TestUtilsRunCommandCaptureStdout(t *testing.T) {
output, err := utils.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 := utils.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 := utils.RunCommand(args)
assert.Error(t, err)
assert.Equal(t, "", output)
}
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) 2017 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
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
},
}

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2017 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
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
}
ctx := createCLIContext(nil)
ctx.App.Name = testAppName
ctx.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)
}

View File

@@ -0,0 +1,683 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"log/syslog"
"net"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/kata-containers/kata-containers/src/runtime/pkg/signals"
pbTypes "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/agent/protocols"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/utils"
"github.com/sirupsen/logrus"
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)
const (
netmonName = "kata-netmon"
kataCmd = "kata-network"
kataCLIAddIfaceCmd = "add-iface"
kataCLIDelIfaceCmd = "del-iface"
kataCLIUpdtRoutesCmd = "update-routes"
kataSuffix = "kata"
// sharedFile is the name of the file that will be used to share
// the data between this process and the kata-runtime process
// responsible for updating the network.
sharedFile = "shared.json"
storageFilePerm = os.FileMode(0640)
storageDirPerm = os.FileMode(0750)
)
var (
// version is the netmon version. This variable is populated at build time.
version = "unknown"
// For simplicity the code will only focus on IPv4 addresses for now.
netlinkFamily = netlink.FAMILY_ALL
storageParentPath = "/var/run/kata-containers/netmon/sbs"
)
type netmonParams struct {
sandboxID string
runtimePath string
logLevel string
debug bool
}
type netmon struct {
netIfaces map[int]pbTypes.Interface
linkUpdateCh chan netlink.LinkUpdate
linkDoneCh chan struct{}
rtUpdateCh chan netlink.RouteUpdate
rtDoneCh chan struct{}
netHandler *netlink.Handle
storagePath string
sharedFile string
netmonParams
}
var netmonLog = logrus.New()
func printVersion() {
fmt.Printf("%s version %s\n", netmonName, version)
}
const componentDescription = `is a network monitoring process that is intended to be started in the
appropriate network namespace so that it can listen to any event related to
link and routes. Whenever a new interface or route is created/updated, it is
responsible for calling into the kata-runtime CLI to ask for the actual
creation/update of the given interface or route.
`
func printComponentDescription() {
fmt.Printf("\n%s %s\n", netmonName, componentDescription)
}
func parseOptions() netmonParams {
var version, help bool
params := netmonParams{}
flag.BoolVar(&help, "h", false, "describe component usage")
flag.BoolVar(&help, "help", false, "")
flag.BoolVar(&params.debug, "d", false, "enable debug mode")
flag.BoolVar(&version, "v", false, "display program version and exit")
flag.BoolVar(&version, "version", false, "")
flag.StringVar(&params.sandboxID, "s", "", "sandbox id (required)")
flag.StringVar(&params.runtimePath, "r", "", "runtime path (required)")
flag.StringVar(&params.logLevel, "log", "warn",
"log messages above specified level: debug, warn, error, fatal or panic")
flag.Parse()
if help {
printComponentDescription()
flag.PrintDefaults()
os.Exit(0)
}
if version {
printVersion()
os.Exit(0)
}
if params.sandboxID == "" {
fmt.Fprintf(os.Stderr, "Error: sandbox id is empty, one must be provided\n")
flag.PrintDefaults()
os.Exit(1)
}
if params.runtimePath == "" {
fmt.Fprintf(os.Stderr, "Error: runtime path is empty, one must be provided\n")
flag.PrintDefaults()
os.Exit(1)
}
return params
}
func newNetmon(params netmonParams) (*netmon, error) {
handler, err := netlink.NewHandle(netlinkFamily)
if err != nil {
return nil, err
}
n := &netmon{
netmonParams: params,
storagePath: filepath.Join(storageParentPath, params.sandboxID),
sharedFile: filepath.Join(storageParentPath, params.sandboxID, sharedFile),
netIfaces: make(map[int]pbTypes.Interface),
linkUpdateCh: make(chan netlink.LinkUpdate),
linkDoneCh: make(chan struct{}),
rtUpdateCh: make(chan netlink.RouteUpdate),
rtDoneCh: make(chan struct{}),
netHandler: handler,
}
if err := os.MkdirAll(n.storagePath, storageDirPerm); err != nil {
return nil, err
}
return n, nil
}
func (n *netmon) cleanup() {
os.RemoveAll(n.storagePath)
n.netHandler.Delete()
close(n.linkDoneCh)
close(n.rtDoneCh)
}
// setupSignalHandler sets up signal handling, starting a go routine to deal
// with signals as they arrive.
func (n *netmon) setupSignalHandler() {
signals.SetLogger(n.logger())
sigCh := make(chan os.Signal, 8)
for _, sig := range signals.HandledSignals() {
signal.Notify(sigCh, sig)
}
go func() {
for {
sig := <-sigCh
nativeSignal, ok := sig.(syscall.Signal)
if !ok {
err := errors.New("unknown signal")
netmonLog.WithError(err).WithField("signal", sig.String()).Error()
continue
}
if signals.FatalSignal(nativeSignal) {
netmonLog.WithField("signal", sig).Error("received fatal signal")
signals.Die(nil)
} else if n.debug && signals.NonFatalSignal(nativeSignal) {
netmonLog.WithField("signal", sig).Debug("handling signal")
signals.Backtrace()
}
}
}()
}
func (n *netmon) logger() *logrus.Entry {
fields := logrus.Fields{
"name": netmonName,
"pid": os.Getpid(),
"source": "netmon",
}
if n.sandboxID != "" {
fields["sandbox"] = n.sandboxID
}
return netmonLog.WithFields(fields)
}
func (n *netmon) setupLogger() error {
level, err := logrus.ParseLevel(n.logLevel)
if err != nil {
return err
}
netmonLog.SetLevel(level)
netmonLog.Formatter = &logrus.TextFormatter{TimestampFormat: time.RFC3339Nano}
hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO|syslog.LOG_USER, netmonName)
if err != nil {
return err
}
netmonLog.AddHook(hook)
announceFields := logrus.Fields{
"runtime-path": n.runtimePath,
"debug": n.debug,
"log-level": n.logLevel,
}
n.logger().WithFields(announceFields).Info("announce")
return nil
}
func (n *netmon) listenNetlinkEvents() error {
if err := netlink.LinkSubscribe(n.linkUpdateCh, n.linkDoneCh); err != nil {
return err
}
return netlink.RouteSubscribe(n.rtUpdateCh, n.rtDoneCh)
}
// convertInterface converts a link and its IP addresses as defined by netlink
// package, into the Interface structure format expected by kata-runtime to
// describe an interface and its associated IP addresses.
func convertInterface(linkAttrs *netlink.LinkAttrs, linkType string, addrs []netlink.Addr) pbTypes.Interface {
if linkAttrs == nil {
netmonLog.Warn("Link attributes are nil")
return pbTypes.Interface{}
}
var ipAddrs []*pbTypes.IPAddress
for _, addr := range addrs {
if addr.IPNet == nil {
continue
}
netMask, _ := addr.Mask.Size()
ipAddr := &pbTypes.IPAddress{
Address: addr.IP.String(),
Mask: fmt.Sprintf("%d", netMask),
}
if addr.IP.To4() != nil {
ipAddr.Family = utils.ConvertNetlinkFamily(netlink.FAMILY_V4)
} else {
ipAddr.Family = utils.ConvertNetlinkFamily(netlink.FAMILY_V6)
}
ipAddrs = append(ipAddrs, ipAddr)
}
iface := pbTypes.Interface{
Device: linkAttrs.Name,
Name: linkAttrs.Name,
IPAddresses: ipAddrs,
Mtu: uint64(linkAttrs.MTU),
HwAddr: linkAttrs.HardwareAddr.String(),
Type: linkType,
}
netmonLog.WithField("interface", iface).Debug("Interface converted")
return iface
}
// convertRoutes converts a list of routes as defined by netlink package,
// into a list of Route structure format expected by kata-runtime to
// describe a set of routes.
func convertRoutes(netRoutes []netlink.Route) []pbTypes.Route {
var routes []pbTypes.Route
for _, netRoute := range netRoutes {
dst := ""
if netRoute.Protocol == unix.RTPROT_KERNEL {
continue
}
if netRoute.Dst != nil {
dst = netRoute.Dst.String()
if netRoute.Dst.IP.To4() != nil || netRoute.Dst.IP.To16() != nil {
dst = netRoute.Dst.String()
} else {
netmonLog.WithField("destination", netRoute.Dst.IP.String()).Warn("Unexpected network address format")
}
}
src := ""
if netRoute.Src != nil {
if netRoute.Src.To4() != nil || netRoute.Src.To16() != nil {
src = netRoute.Src.String()
} else {
netmonLog.WithField("source", netRoute.Src.String()).Warn("Unexpected network address format")
}
}
gw := ""
if netRoute.Gw != nil {
if netRoute.Gw.To4() != nil || netRoute.Gw.To16() != nil {
gw = netRoute.Gw.String()
} else {
netmonLog.WithField("gateway", netRoute.Gw.String()).Warn("Unexpected network address format")
}
}
dev := ""
iface, err := net.InterfaceByIndex(netRoute.LinkIndex)
if err == nil {
dev = iface.Name
}
route := pbTypes.Route{
Dest: dst,
Gateway: gw,
Device: dev,
Source: src,
Scope: uint32(netRoute.Scope),
}
routes = append(routes, route)
}
netmonLog.WithField("routes", routes).Debug("Routes converted")
return routes
}
// scanNetwork lists all the interfaces it can find inside the current
// network namespace, and store them in-memory to keep track of them.
func (n *netmon) scanNetwork() error {
links, err := n.netHandler.LinkList()
if err != nil {
return err
}
for _, link := range links {
addrs, err := n.netHandler.AddrList(link, netlinkFamily)
if err != nil {
return err
}
linkAttrs := link.Attrs()
if linkAttrs == nil {
continue
}
iface := convertInterface(linkAttrs, link.Type(), addrs)
n.netIfaces[linkAttrs.Index] = iface
}
n.logger().Debug("Network scanned")
return nil
}
func (n *netmon) storeDataToSend(data interface{}) error {
// Marshal the data structure into a JSON bytes array.
jsonArray, err := json.Marshal(data)
if err != nil {
return err
}
// Store the JSON bytes array at the specified path.
return ioutil.WriteFile(n.sharedFile, jsonArray, storageFilePerm)
}
func (n *netmon) execKataCmd(subCmd string) error {
execCmd := exec.Command(n.runtimePath, kataCmd, subCmd, n.sandboxID, n.sharedFile)
n.logger().WithField("command", execCmd).Debug("Running runtime command")
// Make use of Run() to ensure the kata-runtime process has correctly
// terminated before to go further.
if err := execCmd.Run(); err != nil {
return err
}
// Remove the shared file after the command returned. At this point
// we know the content of the file is not going to be used anymore,
// and the file path can be reused for further commands.
return os.Remove(n.sharedFile)
}
func (n *netmon) addInterfaceCLI(iface pbTypes.Interface) error {
if err := n.storeDataToSend(iface); err != nil {
return err
}
return n.execKataCmd(kataCLIAddIfaceCmd)
}
func (n *netmon) delInterfaceCLI(iface pbTypes.Interface) error {
if err := n.storeDataToSend(iface); err != nil {
return err
}
return n.execKataCmd(kataCLIDelIfaceCmd)
}
func (n *netmon) updateRoutesCLI(routes []pbTypes.Route) error {
if err := n.storeDataToSend(routes); err != nil {
return err
}
return n.execKataCmd(kataCLIUpdtRoutesCmd)
}
func (n *netmon) updateRoutes() error {
// Get all the routes.
netlinkRoutes, err := n.netHandler.RouteList(nil, netlinkFamily)
if err != nil {
return err
}
// Translate them into Route structures.
routes := convertRoutes(netlinkRoutes)
// Update the routes through the Kata CLI.
return n.updateRoutesCLI(routes)
}
func (n *netmon) handleRTMNewAddr(ev netlink.LinkUpdate) error {
n.logger().Debug("Interface update not supported")
return nil
}
func (n *netmon) handleRTMDelAddr(ev netlink.LinkUpdate) error {
n.logger().Debug("Interface update not supported")
return nil
}
func (n *netmon) handleRTMNewLink(ev netlink.LinkUpdate) error {
// NEWLINK might be a lot of different things. We're interested in
// adding the interface (both to our list and by calling into the
// Kata CLI API) only if this has the flags UP and RUNNING, meaning
// we don't expect any further change on the interface, and that we
// are ready to add it.
linkAttrs := ev.Link.Attrs()
if linkAttrs == nil {
n.logger().Warn("The link attributes are nil")
return nil
}
// First, ignore if the interface name contains "kata". This way we
// are preventing from adding interfaces created by Kata Containers.
if strings.HasSuffix(linkAttrs.Name, kataSuffix) {
n.logger().Debugf("Ignore the interface %s because found %q",
linkAttrs.Name, kataSuffix)
return nil
}
// Check if the interface exist in the internal list.
if _, exist := n.netIfaces[int(ev.Index)]; exist {
n.logger().Debugf("Ignoring interface %s because already exist",
linkAttrs.Name)
return nil
}
// Now, check if the interface has been enabled to UP and RUNNING.
if (ev.Flags&unix.IFF_UP) != unix.IFF_UP ||
(ev.Flags&unix.IFF_RUNNING) != unix.IFF_RUNNING {
n.logger().Debugf("Ignore the interface %s because not UP and RUNNING",
linkAttrs.Name)
return nil
}
// Get the list of IP addresses associated with this interface.
addrs, err := n.netHandler.AddrList(ev.Link, netlinkFamily)
if err != nil {
return err
}
// Convert the interfaces in the appropriate structure format.
iface := convertInterface(linkAttrs, ev.Link.Type(), addrs)
// Add the interface through the Kata CLI.
if err := n.addInterfaceCLI(iface); err != nil {
return err
}
// Add the interface to the internal list.
n.netIfaces[linkAttrs.Index] = iface
// Complete by updating the routes.
return n.updateRoutes()
}
func (n *netmon) handleRTMDelLink(ev netlink.LinkUpdate) error {
// It can only delete if identical interface is found in the internal
// list of interfaces. Otherwise, the deletion will be ignored.
linkAttrs := ev.Link.Attrs()
if linkAttrs == nil {
n.logger().Warn("Link attributes are nil")
return nil
}
// First, ignore if the interface name contains "kata". This way we
// are preventing from deleting interfaces created by Kata Containers.
if strings.Contains(linkAttrs.Name, kataSuffix) {
n.logger().Debugf("Ignore the interface %s because found %q",
linkAttrs.Name, kataSuffix)
return nil
}
// Check if the interface exist in the internal list.
iface, exist := n.netIfaces[int(ev.Index)]
if !exist {
n.logger().Debugf("Ignoring interface %s because not found",
linkAttrs.Name)
return nil
}
if err := n.delInterfaceCLI(iface); err != nil {
return err
}
// Delete the interface from the internal list.
delete(n.netIfaces, linkAttrs.Index)
// Complete by updating the routes.
return n.updateRoutes()
}
func (n *netmon) handleRTMNewRoute(ev netlink.RouteUpdate) error {
// Add the route through updateRoutes(), only if the route refer to an
// interface that already exists in the internal list of interfaces.
if _, exist := n.netIfaces[ev.Route.LinkIndex]; !exist {
n.logger().Debugf("Ignoring route %+v since interface %d not found",
ev.Route, ev.Route.LinkIndex)
return nil
}
return n.updateRoutes()
}
func (n *netmon) handleRTMDelRoute(ev netlink.RouteUpdate) error {
// Remove the route through updateRoutes(), only if the route refer to
// an interface that already exists in the internal list of interfaces.
return n.updateRoutes()
}
func (n *netmon) handleLinkEvent(ev netlink.LinkUpdate) error {
n.logger().Debug("handleLinkEvent: netlink event received")
switch ev.Header.Type {
case unix.NLMSG_DONE:
n.logger().Debug("NLMSG_DONE")
return nil
case unix.NLMSG_ERROR:
n.logger().Error("NLMSG_ERROR")
return fmt.Errorf("Error while listening on netlink socket")
case unix.RTM_NEWADDR:
n.logger().Debug("RTM_NEWADDR")
return n.handleRTMNewAddr(ev)
case unix.RTM_DELADDR:
n.logger().Debug("RTM_DELADDR")
return n.handleRTMDelAddr(ev)
case unix.RTM_NEWLINK:
n.logger().Debug("RTM_NEWLINK")
return n.handleRTMNewLink(ev)
case unix.RTM_DELLINK:
n.logger().Debug("RTM_DELLINK")
return n.handleRTMDelLink(ev)
default:
n.logger().Warnf("Unknown msg type %v", ev.Header.Type)
}
return nil
}
func (n *netmon) handleRouteEvent(ev netlink.RouteUpdate) error {
n.logger().Debug("handleRouteEvent: netlink event received")
switch ev.Type {
case unix.RTM_NEWROUTE:
n.logger().Debug("RTM_NEWROUTE")
return n.handleRTMNewRoute(ev)
case unix.RTM_DELROUTE:
n.logger().Debug("RTM_DELROUTE")
return n.handleRTMDelRoute(ev)
default:
n.logger().Warnf("Unknown msg type %v", ev.Type)
}
return nil
}
func (n *netmon) handleEvents() (err error) {
for {
select {
case ev := <-n.linkUpdateCh:
if err = n.handleLinkEvent(ev); err != nil {
return err
}
case ev := <-n.rtUpdateCh:
if err = n.handleRouteEvent(ev); err != nil {
return err
}
}
}
}
func main() {
// Parse parameters.
params := parseOptions()
// Create netmon handler.
n, err := newNetmon(params)
if err != nil {
netmonLog.WithError(err).Fatal("newNetmon()")
os.Exit(1)
}
defer n.cleanup()
// Init logger.
if err := n.setupLogger(); err != nil {
netmonLog.WithError(err).Fatal("setupLogger()")
os.Exit(1)
}
// Setup signal handlers
n.setupSignalHandler()
// Scan the current interfaces.
if err := n.scanNetwork(); err != nil {
n.logger().WithError(err).Fatal("scanNetwork()")
os.Exit(1)
}
// Subscribe to the link listener.
if err := n.listenNetlinkEvents(); err != nil {
n.logger().WithError(err).Fatal("listenNetlinkEvents()")
os.Exit(1)
}
// Go into the main loop.
if err := n.handleEvents(); err != nil {
n.logger().WithError(err).Fatal("handleEvents()")
os.Exit(1)
}
}

View File

@@ -0,0 +1,701 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"testing"
ktu "github.com/kata-containers/kata-containers/src/runtime/pkg/katatestutils"
pbTypes "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/agent/protocols"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/utils"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
"golang.org/x/sys/unix"
)
const (
testSandboxID = "123456789"
testRuntimePath = "/foo/bar/test-runtime"
testLogLevel = "info"
testStorageParentPath = "/tmp/netmon"
testSharedFile = "foo-shared.json"
testWrongNetlinkFamily = -1
testIfaceName = "test_eth0"
testMTU = 12345
testHwAddr = "02:00:ca:fe:00:48"
testIPAddress = "192.168.0.15"
testIPAddressWithMask = "192.168.0.15/32"
testIP6Address = "2001:db8:1::242:ac11:2"
testIP6AddressWithMask = "2001:db8:1::/64"
testScope = 1
testTxQLen = -1
testIfaceIndex = 5
)
func skipUnlessRoot(t *testing.T) {
tc := ktu.NewTestConstraint(false)
if tc.NotValid(ktu.NeedRoot()) {
t.Skip("Test disabled as requires root user")
}
}
func TestNewNetmon(t *testing.T) {
skipUnlessRoot(t)
// Override storageParentPath
savedStorageParentPath := storageParentPath
storageParentPath = testStorageParentPath
defer func() {
storageParentPath = savedStorageParentPath
}()
params := netmonParams{
sandboxID: testSandboxID,
runtimePath: testRuntimePath,
debug: true,
logLevel: testLogLevel,
}
expected := &netmon{
netmonParams: params,
storagePath: filepath.Join(storageParentPath, params.sandboxID),
sharedFile: filepath.Join(storageParentPath, params.sandboxID, sharedFile),
}
os.RemoveAll(expected.storagePath)
got, err := newNetmon(params)
assert.Nil(t, err)
assert.True(t, reflect.DeepEqual(expected.netmonParams, got.netmonParams),
"Got %+v\nExpected %+v", got.netmonParams, expected.netmonParams)
assert.True(t, reflect.DeepEqual(expected.storagePath, got.storagePath),
"Got %+v\nExpected %+v", got.storagePath, expected.storagePath)
assert.True(t, reflect.DeepEqual(expected.sharedFile, got.sharedFile),
"Got %+v\nExpected %+v", got.sharedFile, expected.sharedFile)
_, err = os.Stat(got.storagePath)
assert.Nil(t, err)
os.RemoveAll(got.storagePath)
}
func TestNewNetmonErrorWrongFamilyType(t *testing.T) {
// Override netlinkFamily
savedNetlinkFamily := netlinkFamily
netlinkFamily = testWrongNetlinkFamily
defer func() {
netlinkFamily = savedNetlinkFamily
}()
n, err := newNetmon(netmonParams{})
assert.NotNil(t, err)
assert.Nil(t, n)
}
func TestCleanup(t *testing.T) {
skipUnlessRoot(t)
// Override storageParentPath
savedStorageParentPath := storageParentPath
storageParentPath = testStorageParentPath
defer func() {
storageParentPath = savedStorageParentPath
}()
handler, err := netlink.NewHandle(netlinkFamily)
assert.Nil(t, err)
n := &netmon{
storagePath: filepath.Join(storageParentPath, testSandboxID),
linkDoneCh: make(chan struct{}),
rtDoneCh: make(chan struct{}),
netHandler: handler,
}
err = os.MkdirAll(n.storagePath, storageDirPerm)
assert.Nil(t, err)
_, err = os.Stat(n.storagePath)
assert.Nil(t, err)
n.cleanup()
_, err = os.Stat(n.storagePath)
assert.NotNil(t, err)
_, ok := (<-n.linkDoneCh)
assert.False(t, ok)
_, ok = (<-n.rtDoneCh)
assert.False(t, ok)
}
func TestLogger(t *testing.T) {
fields := logrus.Fields{
"name": netmonName,
"pid": os.Getpid(),
"source": "netmon",
"sandbox": testSandboxID,
}
expected := netmonLog.WithFields(fields)
n := &netmon{
netmonParams: netmonParams{
sandboxID: testSandboxID,
},
}
got := n.logger()
assert.True(t, reflect.DeepEqual(*expected, *got),
"Got %+v\nExpected %+v", *got, *expected)
}
func TestConvertInterface(t *testing.T) {
hwAddr, err := net.ParseMAC(testHwAddr)
assert.Nil(t, err)
addrs := []netlink.Addr{
{
IPNet: &net.IPNet{
IP: net.ParseIP(testIPAddress),
},
},
{
IPNet: &net.IPNet{
IP: net.ParseIP(testIP6Address),
},
},
}
linkAttrs := &netlink.LinkAttrs{
Name: testIfaceName,
MTU: testMTU,
HardwareAddr: hwAddr,
}
linkType := "link_type_test"
expected := pbTypes.Interface{
Device: testIfaceName,
Name: testIfaceName,
Mtu: uint64(testMTU),
HwAddr: testHwAddr,
IPAddresses: []*pbTypes.IPAddress{
{
Family: utils.ConvertNetlinkFamily(netlink.FAMILY_V4),
Address: testIPAddress,
Mask: "0",
},
{
Family: utils.ConvertNetlinkFamily(netlink.FAMILY_V6),
Address: testIP6Address,
Mask: "0",
},
},
Type: linkType,
}
got := convertInterface(linkAttrs, linkType, addrs)
assert.True(t, reflect.DeepEqual(expected, got),
"Got %+v\nExpected %+v", got, expected)
}
func TestConvertRoutes(t *testing.T) {
ip, ipNet, err := net.ParseCIDR(testIPAddressWithMask)
assert.Nil(t, err)
assert.NotNil(t, ipNet)
_, ip6Net, err := net.ParseCIDR(testIP6AddressWithMask)
assert.Nil(t, err)
assert.NotNil(t, ipNet)
routes := []netlink.Route{
{
Dst: ipNet,
Src: ip,
Gw: ip,
LinkIndex: -1,
Scope: testScope,
},
{
Dst: ip6Net,
Src: nil,
Gw: nil,
LinkIndex: -1,
Scope: testScope,
},
}
expected := []pbTypes.Route{
{
Dest: testIPAddressWithMask,
Gateway: testIPAddress,
Source: testIPAddress,
Scope: uint32(testScope),
},
{
Dest: testIP6AddressWithMask,
Gateway: "",
Source: "",
Scope: uint32(testScope),
},
}
got := convertRoutes(routes)
assert.True(t, reflect.DeepEqual(expected, got),
"Got %+v\nExpected %+v", got, expected)
}
type testTeardownNetwork func()
func testSetupNetwork(t *testing.T) testTeardownNetwork {
skipUnlessRoot(t)
// new temporary namespace so we don't pollute the host
// lock thread since the namespace is thread local
runtime.LockOSThread()
var err error
ns, err := netns.New()
if err != nil {
t.Fatal("Failed to create newns", ns)
}
return func() {
ns.Close()
runtime.UnlockOSThread()
}
}
func testCreateDummyNetwork(t *testing.T, handler *netlink.Handle) (int, pbTypes.Interface) {
hwAddr, err := net.ParseMAC(testHwAddr)
assert.Nil(t, err)
link := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
MTU: testMTU,
TxQLen: testTxQLen,
Name: testIfaceName,
HardwareAddr: hwAddr,
},
}
err = handler.LinkAdd(link)
assert.Nil(t, err)
err = handler.LinkSetUp(link)
assert.Nil(t, err)
attrs := link.Attrs()
assert.NotNil(t, attrs)
addrs, err := handler.AddrList(link, netlinkFamily)
assert.Nil(t, err)
var ipAddrs []*pbTypes.IPAddress
// Scan addresses for ipv6 link local address which is automatically assigned
for _, addr := range addrs {
if addr.IPNet == nil {
continue
}
netMask, _ := addr.Mask.Size()
ipAddr := &pbTypes.IPAddress{
Address: addr.IP.String(),
Mask: fmt.Sprintf("%d", netMask),
}
if addr.IP.To4() != nil {
ipAddr.Family = utils.ConvertNetlinkFamily(netlink.FAMILY_V4)
} else {
ipAddr.Family = utils.ConvertNetlinkFamily(netlink.FAMILY_V6)
}
ipAddrs = append(ipAddrs, ipAddr)
}
iface := pbTypes.Interface{
Device: testIfaceName,
Name: testIfaceName,
Mtu: uint64(testMTU),
HwAddr: testHwAddr,
Type: link.Type(),
IPAddresses: ipAddrs,
}
return attrs.Index, iface
}
func TestScanNetwork(t *testing.T) {
tearDownNetworkCb := testSetupNetwork(t)
defer tearDownNetworkCb()
handler, err := netlink.NewHandle(netlinkFamily)
assert.Nil(t, err)
assert.NotNil(t, handler)
defer handler.Delete()
idx, expected := testCreateDummyNetwork(t, handler)
n := &netmon{
netIfaces: make(map[int]pbTypes.Interface),
netHandler: handler,
}
err = n.scanNetwork()
assert.Nil(t, err)
assert.True(t, reflect.DeepEqual(expected, n.netIfaces[idx]),
"Got %+v\nExpected %+v", n.netIfaces[idx], expected)
}
func TestStoreDataToSend(t *testing.T) {
var got pbTypes.Interface
expected := pbTypes.Interface{
Device: testIfaceName,
Name: testIfaceName,
Mtu: uint64(testMTU),
HwAddr: testHwAddr,
}
n := &netmon{
sharedFile: filepath.Join(testStorageParentPath, testSharedFile),
}
err := os.MkdirAll(testStorageParentPath, storageDirPerm)
defer os.RemoveAll(testStorageParentPath)
assert.Nil(t, err)
err = n.storeDataToSend(expected)
assert.Nil(t, err)
// Check the file has been created, check the content, and delete it.
_, err = os.Stat(n.sharedFile)
assert.Nil(t, err)
byteArray, err := ioutil.ReadFile(n.sharedFile)
assert.Nil(t, err)
err = json.Unmarshal(byteArray, &got)
assert.Nil(t, err)
assert.True(t, reflect.DeepEqual(expected, got),
"Got %+v\nExpected %+v", got, expected)
}
func TestExecKataCmdSuccess(t *testing.T) {
trueBinPath, err := exec.LookPath("true")
assert.Nil(t, err)
assert.NotEmpty(t, trueBinPath)
params := netmonParams{
runtimePath: trueBinPath,
}
n := &netmon{
netmonParams: params,
sharedFile: filepath.Join(testStorageParentPath, testSharedFile),
}
err = os.MkdirAll(testStorageParentPath, storageDirPerm)
assert.Nil(t, err)
defer os.RemoveAll(testStorageParentPath)
file, err := os.Create(n.sharedFile)
assert.Nil(t, err)
assert.NotNil(t, file)
file.Close()
_, err = os.Stat(n.sharedFile)
assert.Nil(t, err)
err = n.execKataCmd("")
assert.Nil(t, err)
_, err = os.Stat(n.sharedFile)
assert.NotNil(t, err)
}
func TestExecKataCmdFailure(t *testing.T) {
falseBinPath, err := exec.LookPath("false")
assert.Nil(t, err)
assert.NotEmpty(t, falseBinPath)
params := netmonParams{
runtimePath: falseBinPath,
}
n := &netmon{
netmonParams: params,
}
err = n.execKataCmd("")
assert.NotNil(t, err)
}
func TestActionsCLI(t *testing.T) {
trueBinPath, err := exec.LookPath("true")
assert.Nil(t, err)
assert.NotEmpty(t, trueBinPath)
params := netmonParams{
runtimePath: trueBinPath,
}
n := &netmon{
netmonParams: params,
sharedFile: filepath.Join(testStorageParentPath, testSharedFile),
}
err = os.MkdirAll(testStorageParentPath, storageDirPerm)
assert.Nil(t, err)
defer os.RemoveAll(testStorageParentPath)
// Test addInterfaceCLI
err = n.addInterfaceCLI(pbTypes.Interface{})
assert.Nil(t, err)
// Test delInterfaceCLI
err = n.delInterfaceCLI(pbTypes.Interface{})
assert.Nil(t, err)
// Test updateRoutesCLI
err = n.updateRoutesCLI([]pbTypes.Route{})
assert.Nil(t, err)
tearDownNetworkCb := testSetupNetwork(t)
defer tearDownNetworkCb()
handler, err := netlink.NewHandle(netlinkFamily)
assert.Nil(t, err)
assert.NotNil(t, handler)
defer handler.Delete()
n.netHandler = handler
// Test updateRoutes
err = n.updateRoutes()
assert.Nil(t, err)
// Test handleRTMDelRoute
err = n.handleRTMDelRoute(netlink.RouteUpdate{})
assert.Nil(t, err)
}
func TestHandleRTMNewAddr(t *testing.T) {
n := &netmon{}
err := n.handleRTMNewAddr(netlink.LinkUpdate{})
assert.Nil(t, err)
}
func TestHandleRTMDelAddr(t *testing.T) {
n := &netmon{}
err := n.handleRTMDelAddr(netlink.LinkUpdate{})
assert.Nil(t, err)
}
func TestHandleRTMNewLink(t *testing.T) {
n := &netmon{}
ev := netlink.LinkUpdate{
Link: &netlink.Dummy{},
}
// LinkAttrs is nil
err := n.handleRTMNewLink(ev)
assert.Nil(t, err)
// Link name contains "kata" suffix
ev = netlink.LinkUpdate{
Link: &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "foo_kata",
},
},
}
err = n.handleRTMNewLink(ev)
assert.Nil(t, err)
// Interface already exist in list
n.netIfaces = make(map[int]pbTypes.Interface)
n.netIfaces[testIfaceIndex] = pbTypes.Interface{}
ev = netlink.LinkUpdate{
Link: &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "foo0",
},
},
}
ev.Index = testIfaceIndex
err = n.handleRTMNewLink(ev)
assert.Nil(t, err)
// Flags are not up and running
n.netIfaces = make(map[int]pbTypes.Interface)
ev = netlink.LinkUpdate{
Link: &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "foo0",
},
},
}
ev.Index = testIfaceIndex
err = n.handleRTMNewLink(ev)
assert.Nil(t, err)
// Invalid link
n.netIfaces = make(map[int]pbTypes.Interface)
ev = netlink.LinkUpdate{
Link: &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "foo0",
},
},
}
ev.Index = testIfaceIndex
ev.Flags = unix.IFF_UP | unix.IFF_RUNNING
handler, err := netlink.NewHandle(netlinkFamily)
assert.Nil(t, err)
assert.NotNil(t, handler)
defer handler.Delete()
n.netHandler = handler
err = n.handleRTMNewLink(ev)
assert.NotNil(t, err)
}
func TestHandleRTMDelLink(t *testing.T) {
n := &netmon{}
ev := netlink.LinkUpdate{
Link: &netlink.Dummy{},
}
// LinkAttrs is nil
err := n.handleRTMDelLink(ev)
assert.Nil(t, err)
// Link name contains "kata" suffix
ev = netlink.LinkUpdate{
Link: &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "foo_kata",
},
},
}
err = n.handleRTMDelLink(ev)
assert.Nil(t, err)
// Interface does not exist in list
n.netIfaces = make(map[int]pbTypes.Interface)
ev = netlink.LinkUpdate{
Link: &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "foo0",
},
},
}
ev.Index = testIfaceIndex
err = n.handleRTMDelLink(ev)
assert.Nil(t, err)
}
func TestHandleRTMNewRouteIfaceNotFound(t *testing.T) {
n := &netmon{
netIfaces: make(map[int]pbTypes.Interface),
}
err := n.handleRTMNewRoute(netlink.RouteUpdate{})
assert.Nil(t, err)
}
func TestHandleLinkEvent(t *testing.T) {
n := &netmon{}
ev := netlink.LinkUpdate{}
// Unknown event
err := n.handleLinkEvent(ev)
assert.Nil(t, err)
// DONE event
ev.Header.Type = unix.NLMSG_DONE
err = n.handleLinkEvent(ev)
assert.Nil(t, err)
// ERROR event
ev.Header.Type = unix.NLMSG_ERROR
err = n.handleLinkEvent(ev)
assert.NotNil(t, err)
// NEWADDR event
ev.Header.Type = unix.RTM_NEWADDR
err = n.handleLinkEvent(ev)
assert.Nil(t, err)
// DELADDR event
ev.Header.Type = unix.RTM_DELADDR
err = n.handleLinkEvent(ev)
assert.Nil(t, err)
// NEWLINK event
ev.Header.Type = unix.RTM_NEWLINK
ev.Link = &netlink.Dummy{}
err = n.handleLinkEvent(ev)
assert.Nil(t, err)
// DELLINK event
ev.Header.Type = unix.RTM_DELLINK
ev.Link = &netlink.Dummy{}
err = n.handleLinkEvent(ev)
assert.Nil(t, err)
}
func TestHandleRouteEvent(t *testing.T) {
n := &netmon{}
ev := netlink.RouteUpdate{}
// Unknown event
err := n.handleRouteEvent(ev)
assert.Nil(t, err)
// RTM_NEWROUTE event
ev.Type = unix.RTM_NEWROUTE
err = n.handleRouteEvent(ev)
assert.Nil(t, err)
trueBinPath, err := exec.LookPath("true")
assert.Nil(t, err)
assert.NotEmpty(t, trueBinPath)
n.runtimePath = trueBinPath
n.sharedFile = filepath.Join(testStorageParentPath, testSharedFile)
err = os.MkdirAll(testStorageParentPath, storageDirPerm)
assert.Nil(t, err)
defer os.RemoveAll(testStorageParentPath)
tearDownNetworkCb := testSetupNetwork(t)
defer tearDownNetworkCb()
handler, err := netlink.NewHandle(netlinkFamily)
assert.Nil(t, err)
assert.NotNil(t, handler)
defer handler.Delete()
n.netHandler = handler
// RTM_DELROUTE event
ev.Type = unix.RTM_DELROUTE
err = n.handleRouteEvent(ev)
assert.Nil(t, err)
}