mirror of
https://github.com/aljazceru/kata-containers.git
synced 2026-01-05 07:24:20 +01:00
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:
40
src/runtime/cmd/config-generated.go.in
Normal file
40
src/runtime/cmd/config-generated.go.in
Normal 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@"
|
||||
31
src/runtime/cmd/containerd-shim-kata-v2/main.go
Normal file
31
src/runtime/cmd/containerd-shim-kata-v2/main.go
Normal 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)
|
||||
}
|
||||
139
src/runtime/cmd/kata-monitor/main.go
Normal file
139
src/runtime/cmd/kata-monitor/main.go
Normal 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)
|
||||
}
|
||||
28
src/runtime/cmd/kata-runtime/exit.go
Normal file
28
src/runtime/cmd/kata-runtime/exit.go
Normal 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)
|
||||
}
|
||||
42
src/runtime/cmd/kata-runtime/exit_test.go
Normal file
42
src/runtime/cmd/kata-runtime/exit_test.go
Normal 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)
|
||||
}
|
||||
326
src/runtime/cmd/kata-runtime/factory.go
Normal file
326
src/runtime/cmd/kata-runtime/factory.go
Normal 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
|
||||
},
|
||||
}
|
||||
158
src/runtime/cmd/kata-runtime/factory_test.go
Normal file
158
src/runtime/cmd/kata-runtime/factory_test.go
Normal 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)
|
||||
}
|
||||
540
src/runtime/cmd/kata-runtime/kata-check.go
Normal file
540
src/runtime/cmd/kata-runtime/kata-check.go
Normal 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
|
||||
}
|
||||
324
src/runtime/cmd/kata-runtime/kata-check_amd64.go
Normal file
324
src/runtime/cmd/kata-runtime/kata-check_amd64.go
Normal 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()
|
||||
}
|
||||
492
src/runtime/cmd/kata-runtime/kata-check_amd64_test.go
Normal file
492
src/runtime/cmd/kata-runtime/kata-check_amd64_test.go
Normal 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)
|
||||
}
|
||||
171
src/runtime/cmd/kata-runtime/kata-check_arm64.go
Normal file
171
src/runtime/cmd/kata-runtime/kata-check_arm64.go
Normal 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
|
||||
}
|
||||
112
src/runtime/cmd/kata-runtime/kata-check_arm64_test.go
Normal file
112
src/runtime/cmd/kata-runtime/kata-check_arm64_test.go
Normal 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)
|
||||
}
|
||||
63
src/runtime/cmd/kata-runtime/kata-check_data_amd64_test.go
Normal file
63
src/runtime/cmd/kata-runtime/kata-check_data_amd64_test.go
Normal 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:
|
||||
|
||||
`
|
||||
27
src/runtime/cmd/kata-runtime/kata-check_data_arm64_test.go
Normal file
27
src/runtime/cmd/kata-runtime/kata-check_data_arm64_test.go
Normal 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
|
||||
|
||||
`
|
||||
24
src/runtime/cmd/kata-runtime/kata-check_data_ppc64le_test.go
Normal file
24
src/runtime/cmd/kata-runtime/kata-check_data_ppc64le_test.go
Normal 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)
|
||||
}
|
||||
21
src/runtime/cmd/kata-runtime/kata-check_data_s390x_test.go
Normal file
21
src/runtime/cmd/kata-runtime/kata-check_data_s390x_test.go
Normal 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
|
||||
`
|
||||
47
src/runtime/cmd/kata-runtime/kata-check_generic_test.go
Normal file
47
src/runtime/cmd/kata-runtime/kata-check_generic_test.go
Normal 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)
|
||||
}
|
||||
190
src/runtime/cmd/kata-runtime/kata-check_ppc64le.go
Normal file
190
src/runtime/cmd/kata-runtime/kata-check_ppc64le.go
Normal 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
|
||||
}
|
||||
175
src/runtime/cmd/kata-runtime/kata-check_ppc64le_test.go
Normal file
175
src/runtime/cmd/kata-runtime/kata-check_ppc64le_test.go
Normal 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)
|
||||
}
|
||||
131
src/runtime/cmd/kata-runtime/kata-check_s390x.go
Normal file
131
src/runtime/cmd/kata-runtime/kata-check_s390x.go
Normal 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()
|
||||
}
|
||||
168
src/runtime/cmd/kata-runtime/kata-check_s390x_test.go
Normal file
168
src/runtime/cmd/kata-runtime/kata-check_s390x_test.go
Normal 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)
|
||||
}
|
||||
920
src/runtime/cmd/kata-runtime/kata-check_test.go
Normal file
920
src/runtime/cmd/kata-runtime/kata-check_test.go
Normal 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)
|
||||
}
|
||||
452
src/runtime/cmd/kata-runtime/kata-env.go
Normal file
452
src/runtime/cmd/kata-runtime/kata-env.go
Normal 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)
|
||||
},
|
||||
}
|
||||
66
src/runtime/cmd/kata-runtime/kata-env_amd64_test.go
Normal file
66
src/runtime/cmd/kata-runtime/kata-env_amd64_test.go
Normal 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)
|
||||
}
|
||||
21
src/runtime/cmd/kata-runtime/kata-env_arm64_test.go
Normal file
21
src/runtime/cmd/kata-runtime/kata-env_arm64_test.go
Normal 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)
|
||||
}
|
||||
56
src/runtime/cmd/kata-runtime/kata-env_generic_test.go
Normal file
56
src/runtime/cmd/kata-runtime/kata-env_generic_test.go
Normal 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)
|
||||
}
|
||||
19
src/runtime/cmd/kata-runtime/kata-env_ppc64le_test.go
Normal file
19
src/runtime/cmd/kata-runtime/kata-env_ppc64le_test.go
Normal 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)
|
||||
}
|
||||
13
src/runtime/cmd/kata-runtime/kata-env_s390x_test.go
Normal file
13
src/runtime/cmd/kata-runtime/kata-env_s390x_test.go
Normal 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)
|
||||
}
|
||||
1026
src/runtime/cmd/kata-runtime/kata-env_test.go
Normal file
1026
src/runtime/cmd/kata-runtime/kata-env_test.go
Normal file
File diff suppressed because it is too large
Load Diff
208
src/runtime/cmd/kata-runtime/kata-exec.go
Normal file
208
src/runtime/cmd/kata-runtime/kata-exec.go
Normal 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)
|
||||
}
|
||||
38
src/runtime/cmd/kata-runtime/kata-metrics.go
Normal file
38
src/runtime/cmd/kata-runtime/kata-metrics.go
Normal 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
|
||||
},
|
||||
}
|
||||
498
src/runtime/cmd/kata-runtime/main.go
Normal file
498
src/runtime/cmd/kata-runtime/main.go
Normal 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)
|
||||
}
|
||||
1012
src/runtime/cmd/kata-runtime/main_test.go
Normal file
1012
src/runtime/cmd/kata-runtime/main_test.go
Normal file
File diff suppressed because it is too large
Load Diff
418
src/runtime/cmd/kata-runtime/release.go
Normal file
418
src/runtime/cmd/kata-runtime/release.go
Normal 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)
|
||||
}
|
||||
519
src/runtime/cmd/kata-runtime/release_test.go
Normal file
519
src/runtime/cmd/kata-runtime/release_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
191
src/runtime/cmd/kata-runtime/utils.go
Normal file
191
src/runtime/cmd/kata-runtime/utils.go
Normal 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,
|
||||
}
|
||||
|
||||
}
|
||||
221
src/runtime/cmd/kata-runtime/utils_test.go
Normal file
221
src/runtime/cmd/kata-runtime/utils_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
19
src/runtime/cmd/kata-runtime/version.go
Normal file
19
src/runtime/cmd/kata-runtime/version.go
Normal 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
|
||||
},
|
||||
}
|
||||
53
src/runtime/cmd/kata-runtime/version_test.go
Normal file
53
src/runtime/cmd/kata-runtime/version_test.go
Normal 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)
|
||||
}
|
||||
683
src/runtime/cmd/netmon/netmon.go
Normal file
683
src/runtime/cmd/netmon/netmon.go
Normal 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(¶ms.debug, "d", false, "enable debug mode")
|
||||
flag.BoolVar(&version, "v", false, "display program version and exit")
|
||||
flag.BoolVar(&version, "version", false, "")
|
||||
flag.StringVar(¶ms.sandboxID, "s", "", "sandbox id (required)")
|
||||
flag.StringVar(¶ms.runtimePath, "r", "", "runtime path (required)")
|
||||
flag.StringVar(¶ms.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)
|
||||
}
|
||||
}
|
||||
701
src/runtime/cmd/netmon/netmon_test.go
Normal file
701
src/runtime/cmd/netmon/netmon_test.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user