Files
kata-containers/virtcontainers/hack/virtc/main.go
Peng Tao 25d21060e3 Merge pull request #1412 from lifupan/shimv2mount
shimv2: optionally plug rootfs block storage instead of mounting it
2019-04-02 15:30:40 +08:00

929 lines
20 KiB
Go

// Copyright (c) 2016 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"context"
"errors"
"fmt"
"os"
"strings"
"text/tabwriter"
"github.com/kata-containers/runtime/virtcontainers/pkg/uuid"
"github.com/kata-containers/runtime/virtcontainers/types"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
vc "github.com/kata-containers/runtime/virtcontainers"
)
var virtcLog *logrus.Entry
var listFormat = "%s\t%s\t%s\t%s\n"
var statusFormat = "%s\t%s\n"
var ctx = context.Background()
var (
errNeedContainerID = errors.New("Container ID cannot be empty")
errNeedSandboxID = errors.New("Sandbox ID cannot be empty")
)
var sandboxConfigFlags = []cli.Flag{
cli.GenericFlag{
Name: "agent",
Value: new(vc.AgentType),
Usage: "the guest agent",
},
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the sandbox identifier (default: auto-generated)",
},
cli.StringFlag{
Name: "machine-type",
Value: vc.QemuPC,
Usage: "hypervisor machine type",
},
cli.GenericFlag{
Name: "proxy",
Value: new(vc.ProxyType),
Usage: "the agent's proxy",
},
cli.StringFlag{
Name: "proxy-path",
Value: "",
Usage: "path to proxy binary",
},
cli.GenericFlag{
Name: "shim",
Value: new(vc.ShimType),
Usage: "the shim type",
},
cli.StringFlag{
Name: "shim-path",
Value: "",
Usage: "the shim binary path",
},
cli.StringFlag{
Name: "hyper-ctl-sock-name",
Value: "",
Usage: "the hyperstart control socket name",
},
cli.StringFlag{
Name: "hyper-tty-sock-name",
Value: "",
Usage: "the hyperstart tty socket name",
},
cli.UintFlag{
Name: "cpus",
Value: 0,
Usage: "the number of virtual cpus available for this sandbox",
},
cli.UintFlag{
Name: "memory",
Value: 0,
Usage: "the amount of memory available for this sandbox in MiB",
},
}
var ccKernelParams = []vc.Param{
{
Key: "init",
Value: "/usr/lib/systemd/systemd",
},
{
Key: "systemd.unit",
Value: "clear-containers.target",
},
{
Key: "systemd.mask",
Value: "systemd-networkd.service",
},
{
Key: "systemd.mask",
Value: "systemd-networkd.socket",
},
}
func buildKernelParams(config *vc.HypervisorConfig) error {
for _, p := range ccKernelParams {
if err := config.AddKernelParam(p); err != nil {
return err
}
}
return nil
}
func buildSandboxConfig(context *cli.Context) (vc.SandboxConfig, error) {
var agConfig interface{}
hyperCtlSockName := context.String("hyper-ctl-sock-name")
hyperTtySockName := context.String("hyper-tty-sock-name")
proxyPath := context.String("proxy-path")
shimPath := context.String("shim-path")
machineType := context.String("machine-type")
vmMemory := context.Uint("vm-memory")
agentType, ok := context.Generic("agent").(*vc.AgentType)
if !ok {
return vc.SandboxConfig{}, fmt.Errorf("Could not convert agent type")
}
proxyType, ok := context.Generic("proxy").(*vc.ProxyType)
if !ok {
return vc.SandboxConfig{}, fmt.Errorf("Could not convert proxy type")
}
shimType, ok := context.Generic("shim").(*vc.ShimType)
if !ok {
return vc.SandboxConfig{}, fmt.Errorf("Could not convert shim type")
}
kernelPath := "/usr/share/clear-containers/vmlinuz.container"
if machineType == vc.QemuPCLite {
kernelPath = "/usr/share/clear-containers/vmlinux.container"
}
hypervisorConfig := vc.HypervisorConfig{
KernelPath: kernelPath,
ImagePath: "/usr/share/clear-containers/clear-containers.img",
HypervisorMachineType: machineType,
MemorySize: uint32(vmMemory),
}
if err := buildKernelParams(&hypervisorConfig); err != nil {
return vc.SandboxConfig{}, err
}
netConfig := vc.NetworkConfig{}
switch *agentType {
case vc.HyperstartAgent:
agConfig = vc.HyperConfig{
SockCtlName: hyperCtlSockName,
SockTtyName: hyperTtySockName,
}
default:
agConfig = nil
}
proxyConfig := getProxyConfig(*proxyType, proxyPath)
shimConfig := getShimConfig(*shimType, shimPath)
id := context.String("id")
if id == "" {
// auto-generate sandbox name
id = uuid.Generate().String()
}
sandboxConfig := vc.SandboxConfig{
ID: id,
HypervisorType: vc.QemuHypervisor,
HypervisorConfig: hypervisorConfig,
AgentType: *agentType,
AgentConfig: agConfig,
NetworkConfig: netConfig,
ProxyType: *proxyType,
ProxyConfig: proxyConfig,
ShimType: *shimType,
ShimConfig: shimConfig,
Containers: []vc.ContainerConfig{},
}
return sandboxConfig, nil
}
func getProxyConfig(proxyType vc.ProxyType, path string) vc.ProxyConfig {
var proxyConfig vc.ProxyConfig
switch proxyType {
case vc.KataProxyType:
fallthrough
case vc.CCProxyType:
proxyConfig = vc.ProxyConfig{
Path: path,
}
}
return proxyConfig
}
func getShimConfig(shimType vc.ShimType, path string) interface{} {
var shimConfig interface{}
switch shimType {
case vc.CCShimType, vc.KataShimType:
shimConfig = vc.ShimConfig{
Path: path,
}
default:
shimConfig = nil
}
return shimConfig
}
// checkRequiredSandboxArgs checks to ensure the required command-line
// arguments have been specified for the sandbox sub-command specified by
// the context argument.
func checkRequiredSandboxArgs(context *cli.Context) error {
if context == nil {
return fmt.Errorf("BUG: need Context")
}
// sub-sub-command name
name := context.Command.Name
switch name {
case "create":
fallthrough
case "list":
fallthrough
case "run":
// these commands don't require any arguments
return nil
}
id := context.String("id")
if id == "" {
return errNeedSandboxID
}
return nil
}
// checkRequiredContainerArgs checks to ensure the required command-line
// arguments have been specified for the container sub-command specified
// by the context argument.
func checkRequiredContainerArgs(context *cli.Context) error {
if context == nil {
return fmt.Errorf("BUG: need Context")
}
// sub-sub-command name
name := context.Command.Name
sandboxID := context.String("sandbox-id")
if sandboxID == "" {
return errNeedSandboxID
}
rootfs := context.String("rootfs")
if name == "create" && rootfs == "" {
return fmt.Errorf("%s: need rootfs", name)
}
id := context.String("id")
if id == "" {
return errNeedContainerID
}
return nil
}
func runSandbox(context *cli.Context) error {
sandboxConfig, err := buildSandboxConfig(context)
if err != nil {
return fmt.Errorf("Could not build sandbox config: %s", err)
}
_, err = vc.RunSandbox(ctx, sandboxConfig, nil)
if err != nil {
return fmt.Errorf("Could not run sandbox: %s", err)
}
return nil
}
func createSandbox(context *cli.Context) error {
sandboxConfig, err := buildSandboxConfig(context)
if err != nil {
return fmt.Errorf("Could not build sandbox config: %s", err)
}
p, err := vc.CreateSandbox(ctx, sandboxConfig, nil)
if err != nil {
return fmt.Errorf("Could not create sandbox: %s", err)
}
fmt.Printf("Sandbox %s created\n", p.ID())
return nil
}
func checkSandboxArgs(context *cli.Context, f func(context *cli.Context) error) error {
if err := checkRequiredSandboxArgs(context); err != nil {
return err
}
return f(context)
}
func checkContainerArgs(context *cli.Context, f func(context *cli.Context) error) error {
if err := checkRequiredContainerArgs(context); err != nil {
return err
}
return f(context)
}
func deleteSandbox(context *cli.Context) error {
p, err := vc.DeleteSandbox(ctx, context.String("id"))
if err != nil {
return fmt.Errorf("Could not delete sandbox: %s", err)
}
fmt.Printf("Sandbox %s deleted\n", p.ID())
return nil
}
func startSandbox(context *cli.Context) error {
p, err := vc.StartSandbox(ctx, context.String("id"))
if err != nil {
return fmt.Errorf("Could not start sandbox: %s", err)
}
fmt.Printf("Sandbox %s started\n", p.ID())
return nil
}
func stopSandbox(context *cli.Context) error {
p, err := vc.StopSandbox(ctx, context.String("id"))
if err != nil {
return fmt.Errorf("Could not stop sandbox: %s", err)
}
fmt.Printf("Sandbox %s stopped\n", p.ID())
return nil
}
func pauseSandbox(context *cli.Context) error {
p, err := vc.PauseSandbox(ctx, context.String("id"))
if err != nil {
return fmt.Errorf("Could not pause sandbox: %s", err)
}
fmt.Printf("Sandbox %s paused\n", p.ID())
return nil
}
func resumeSandbox(context *cli.Context) error {
p, err := vc.ResumeSandbox(ctx, context.String("id"))
if err != nil {
return fmt.Errorf("Could not resume sandbox: %s", err)
}
fmt.Printf("Sandbox %s resumed\n", p.ID())
return nil
}
func listSandboxes(context *cli.Context) error {
sandboxStatusList, err := vc.ListSandbox(ctx)
if err != nil {
return fmt.Errorf("Could not list sandbox: %s", err)
}
w := tabwriter.NewWriter(os.Stdout, 2, 8, 1, '\t', 0)
fmt.Fprintf(w, listFormat, "SB ID", "STATE", "HYPERVISOR", "AGENT")
for _, sandboxStatus := range sandboxStatusList {
fmt.Fprintf(w, listFormat,
sandboxStatus.ID, sandboxStatus.State.State, sandboxStatus.Hypervisor, sandboxStatus.Agent)
}
w.Flush()
return nil
}
func statusSandbox(context *cli.Context) error {
sandboxStatus, err := vc.StatusSandbox(ctx, context.String("id"))
if err != nil {
return fmt.Errorf("Could not get sandbox status: %s", err)
}
w := tabwriter.NewWriter(os.Stdout, 2, 8, 1, '\t', 0)
fmt.Fprintf(w, listFormat, "SB ID", "STATE", "HYPERVISOR", "AGENT")
fmt.Fprintf(w, listFormat+"\n",
sandboxStatus.ID, sandboxStatus.State.State, sandboxStatus.Hypervisor, sandboxStatus.Agent)
fmt.Fprintf(w, statusFormat, "CONTAINER ID", "STATE")
for _, contStatus := range sandboxStatus.ContainersStatus {
fmt.Fprintf(w, statusFormat, contStatus.ID, contStatus.State.State)
}
w.Flush()
return nil
}
var runSandboxCommand = cli.Command{
Name: "run",
Usage: "run a sandbox",
Flags: sandboxConfigFlags,
Action: func(context *cli.Context) error {
return checkSandboxArgs(context, runSandbox)
},
}
var createSandboxCommand = cli.Command{
Name: "create",
Usage: "create a sandbox",
Flags: sandboxConfigFlags,
Action: func(context *cli.Context) error {
return checkSandboxArgs(context, createSandbox)
},
}
var deleteSandboxCommand = cli.Command{
Name: "delete",
Usage: "delete an existing sandbox",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the sandbox identifier",
},
},
Action: func(context *cli.Context) error {
return checkSandboxArgs(context, deleteSandbox)
},
}
var startSandboxCommand = cli.Command{
Name: "start",
Usage: "start an existing sandbox",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the sandbox identifier",
},
},
Action: func(context *cli.Context) error {
return checkSandboxArgs(context, startSandbox)
},
}
var stopSandboxCommand = cli.Command{
Name: "stop",
Usage: "stop an existing sandbox",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the sandbox identifier",
},
},
Action: func(context *cli.Context) error {
return checkSandboxArgs(context, stopSandbox)
},
}
var listSandboxesCommand = cli.Command{
Name: "list",
Usage: "list all existing sandboxes",
Action: func(context *cli.Context) error {
return checkSandboxArgs(context, listSandboxes)
},
}
var statusSandboxCommand = cli.Command{
Name: "status",
Usage: "returns a detailed sandbox status",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the sandbox identifier",
},
},
Action: func(context *cli.Context) error {
return checkSandboxArgs(context, statusSandbox)
},
}
var pauseSandboxCommand = cli.Command{
Name: "pause",
Usage: "pause an existing sandbox",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the sandbox identifier",
},
},
Action: func(context *cli.Context) error {
return checkSandboxArgs(context, pauseSandbox)
},
}
var resumeSandboxCommand = cli.Command{
Name: "resume",
Usage: "unpause a paused sandbox",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the sandbox identifier",
},
},
Action: func(context *cli.Context) error {
return checkSandboxArgs(context, resumeSandbox)
},
}
func createContainer(context *cli.Context) error {
console := context.String("console")
interactive := false
if console != "" {
interactive = true
}
envs := []types.EnvVar{
{
Var: "PATH",
Value: "/bin:/usr/bin:/sbin:/usr/sbin",
},
}
cmd := types.Cmd{
Args: strings.Split(context.String("cmd"), " "),
Envs: envs,
WorkDir: "/",
Interactive: interactive,
Console: console,
}
id := context.String("id")
if id == "" {
// auto-generate container name
id = uuid.Generate().String()
}
containerConfig := vc.ContainerConfig{
ID: id,
RootFs: vc.RootFs{Target: context.String("rootfs"), Mounted: true},
Cmd: cmd,
}
_, c, err := vc.CreateContainer(ctx, context.String("sandbox-id"), containerConfig)
if err != nil {
return fmt.Errorf("Could not create container: %s", err)
}
fmt.Printf("Container %s created\n", c.ID())
return nil
}
func deleteContainer(context *cli.Context) error {
c, err := vc.DeleteContainer(ctx, context.String("sandbox-id"), context.String("id"))
if err != nil {
return fmt.Errorf("Could not delete container: %s", err)
}
fmt.Printf("Container %s deleted\n", c.ID())
return nil
}
func startContainer(context *cli.Context) error {
c, err := vc.StartContainer(ctx, context.String("sandbox-id"), context.String("id"))
if err != nil {
return fmt.Errorf("Could not start container: %s", err)
}
fmt.Printf("Container %s started\n", c.ID())
return nil
}
func stopContainer(context *cli.Context) error {
c, err := vc.StopContainer(ctx, context.String("sandbox-id"), context.String("id"))
if err != nil {
return fmt.Errorf("Could not stop container: %s", err)
}
fmt.Printf("Container %s stopped\n", c.ID())
return nil
}
func enterContainer(context *cli.Context) error {
console := context.String("console")
interactive := false
if console != "" {
interactive = true
}
envs := []types.EnvVar{
{
Var: "PATH",
Value: "/bin:/usr/bin:/sbin:/usr/sbin",
},
}
cmd := types.Cmd{
Args: strings.Split(context.String("cmd"), " "),
Envs: envs,
WorkDir: "/",
Interactive: interactive,
Console: console,
}
_, c, _, err := vc.EnterContainer(ctx, context.String("sandbox-id"), context.String("id"), cmd)
if err != nil {
return fmt.Errorf("Could not enter container: %s", err)
}
fmt.Printf("Container %s entered\n", c.ID())
return nil
}
func statusContainer(context *cli.Context) error {
contStatus, err := vc.StatusContainer(ctx, context.String("sandbox-id"), context.String("id"))
if err != nil {
return fmt.Errorf("Could not get container status: %s", err)
}
w := tabwriter.NewWriter(os.Stdout, 2, 8, 1, '\t', 0)
fmt.Fprintf(w, statusFormat, "CONTAINER ID", "STATE")
fmt.Fprintf(w, statusFormat, contStatus.ID, contStatus.State.State)
w.Flush()
return nil
}
var createContainerCommand = cli.Command{
Name: "create",
Usage: "create a container",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the container identifier (default: auto-generated)",
},
cli.StringFlag{
Name: "sandbox-id",
Value: "",
Usage: "the sandbox identifier",
},
cli.StringFlag{
Name: "rootfs",
Value: "",
Usage: "the container rootfs directory",
},
cli.StringFlag{
Name: "cmd",
Value: "",
Usage: "the command executed inside the container",
},
cli.StringFlag{
Name: "console",
Value: "",
Usage: "the container console",
},
},
Action: func(context *cli.Context) error {
return checkContainerArgs(context, createContainer)
},
}
var deleteContainerCommand = cli.Command{
Name: "delete",
Usage: "delete an existing container",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the container identifier",
},
cli.StringFlag{
Name: "sandbox-id",
Value: "",
Usage: "the sandbox identifier",
},
},
Action: func(context *cli.Context) error {
return checkContainerArgs(context, deleteContainer)
},
}
var startContainerCommand = cli.Command{
Name: "start",
Usage: "start an existing container",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the container identifier",
},
cli.StringFlag{
Name: "sandbox-id",
Value: "",
Usage: "the sandbox identifier",
},
},
Action: func(context *cli.Context) error {
return checkContainerArgs(context, startContainer)
},
}
var stopContainerCommand = cli.Command{
Name: "stop",
Usage: "stop an existing container",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the container identifier",
},
cli.StringFlag{
Name: "sandbox-id",
Value: "",
Usage: "the sandbox identifier",
},
},
Action: func(context *cli.Context) error {
return checkContainerArgs(context, stopContainer)
},
}
var enterContainerCommand = cli.Command{
Name: "enter",
Usage: "enter an existing container",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the container identifier",
},
cli.StringFlag{
Name: "sandbox-id",
Value: "",
Usage: "the sandbox identifier",
},
cli.StringFlag{
Name: "cmd",
Value: "echo",
Usage: "the command executed inside the container",
},
cli.StringFlag{
Name: "console",
Value: "",
Usage: "the process console",
},
},
Action: func(context *cli.Context) error {
return checkContainerArgs(context, enterContainer)
},
}
var statusContainerCommand = cli.Command{
Name: "status",
Usage: "returns detailed container status",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "the container identifier",
},
cli.StringFlag{
Name: "sandbox-id",
Value: "",
Usage: "the sandbox identifier",
},
},
Action: func(context *cli.Context) error {
return checkContainerArgs(context, statusContainer)
},
}
func main() {
cli.VersionFlag = cli.BoolFlag{
Name: "version",
Usage: "print the version",
}
virtc := cli.NewApp()
virtc.Name = "VirtContainers CLI"
virtc.Version = "0.0.1"
virtc.Flags = []cli.Flag{
cli.BoolFlag{
Name: "debug",
Usage: "enable debug output for logging",
},
cli.StringFlag{
Name: "log",
Value: "",
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')",
},
}
virtc.Commands = []cli.Command{
{
Name: "sandbox",
Usage: "sandbox commands",
Subcommands: []cli.Command{
createSandboxCommand,
deleteSandboxCommand,
listSandboxesCommand,
pauseSandboxCommand,
resumeSandboxCommand,
runSandboxCommand,
startSandboxCommand,
stopSandboxCommand,
statusSandboxCommand,
},
},
{
Name: "container",
Usage: "container commands",
Subcommands: []cli.Command{
createContainerCommand,
deleteContainerCommand,
startContainerCommand,
stopContainerCommand,
enterContainerCommand,
statusContainerCommand,
},
},
}
virtc.Before = func(context *cli.Context) error {
if context.GlobalBool("debug") {
virtcLog.Level = logrus.DebugLevel
}
if path := context.GlobalString("log"); path != "" {
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0640)
if err != nil {
return err
}
virtcLog.Logger.Out = f
}
switch context.GlobalString("log-format") {
case "text":
// retain logrus's default.
case "json":
virtcLog.Logger.Formatter = new(logrus.JSONFormatter)
default:
return fmt.Errorf("unknown log-format %q", context.GlobalString("log-format"))
}
// Set virtcontainers logger.
vc.SetLogger(ctx, virtcLog)
return nil
}
err := virtc.Run(os.Args)
if err != nil {
virtcLog.Fatal(err)
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}