diff --git a/cli/config.go b/cli/config.go index cc6e9bb006..a800f19f71 100644 --- a/cli/config.go +++ b/cli/config.go @@ -62,6 +62,11 @@ type tomlConfig struct { Shim map[string]shim Agent map[string]agent Runtime runtime + Factory factory +} + +type factory struct { + Template bool `toml:"enable_template"` } type hypervisor struct { @@ -353,6 +358,10 @@ func newQemuHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) { }, nil } +func newFactoryConfig(f factory) (oci.FactoryConfig, error) { + return oci.FactoryConfig{Template: f.Template}, nil +} + func newShimConfig(s shim) (vc.ShimConfig, error) { path, err := s.path() if err != nil { @@ -423,6 +432,12 @@ func updateRuntimeConfig(configPath string, tomlConf tomlConfig, config *oci.Run config.ShimConfig = shConfig } + fConfig, err := newFactoryConfig(tomlConf.Factory) + if err != nil { + return fmt.Errorf("%v: %v", configPath, err) + } + config.FactoryConfig = fConfig + return nil } diff --git a/cli/config/configuration.toml.in b/cli/config/configuration.toml.in index b6e91a31ac..b7517284ec 100644 --- a/cli/config/configuration.toml.in +++ b/cli/config/configuration.toml.in @@ -134,6 +134,18 @@ enable_iothreads = @DEFENABLEIOTHREADS@ # used for 9p packet payload. #msize_9p = @DEFMSIZE9P@ +[factory] +# VM templating support. Once enabled, new VMs are created from template +# using vm cloning. They will share the same initial kernel, initramfs and +# agent memory by mapping it readonly. It helps speeding up new container +# creation and saves a lot of memory if there are many kata containers running +# on the same host. +# +# When disabled, new VMs are created from scratch. +# +# Default false +#enable_template = true + [proxy.@PROJECT_TYPE@] path = "@PROXYPATH@" diff --git a/cli/config_test.go b/cli/config_test.go index c301da3ef7..9b79d5bab1 100644 --- a/cli/config_test.go +++ b/cli/config_test.go @@ -1146,3 +1146,19 @@ func TestUpdateRuntimeConfigurationVMConfig(t *testing.T) { assert.Equal(expectedVMConfig, config.VMConfig) } + +func TestUpdateRuntimeConfigurationFactoryConfig(t *testing.T) { + assert := assert.New(t) + + config := oci.RuntimeConfig{} + expectedFactoryConfig := oci.FactoryConfig{ + Template: true, + } + + tomlConf := tomlConfig{Factory: factory{Template: true}} + + err := updateRuntimeConfig("", tomlConf, &config) + assert.NoError(err) + + assert.Equal(expectedFactoryConfig, config.FactoryConfig) +} diff --git a/cli/create.go b/cli/create.go index 1ff6cbcf58..982d5c31ae 100644 --- a/cli/create.go +++ b/cli/create.go @@ -15,6 +15,7 @@ import ( "strings" vc "github.com/kata-containers/runtime/virtcontainers" + vf "github.com/kata-containers/runtime/virtcontainers/factory" "github.com/kata-containers/runtime/virtcontainers/pkg/oci" "github.com/urfave/cli" ) @@ -106,6 +107,25 @@ func create(containerID, bundlePath, console, pidFilePath string, detach bool, return err } + if runtimeConfig.FactoryConfig.Template { + factoryConfig := vf.Config{ + Template: true, + VMConfig: vc.VMConfig{ + HypervisorType: runtimeConfig.HypervisorType, + HypervisorConfig: runtimeConfig.HypervisorConfig, + AgentType: runtimeConfig.AgentType, + AgentConfig: runtimeConfig.AgentConfig, + }, + } + kataLog.WithField("factory", factoryConfig).Info("load vm factory") + f, err := vf.NewFactory(factoryConfig, true) + if err != nil { + kataLog.WithError(err).Info("load vm factory failed") + } else { + vci.SetFactory(f) + } + } + disableOutput := noNeedForOutput(detach, ociSpec.Process.Terminal) var process vc.Process diff --git a/cli/factory.go b/cli/factory.go new file mode 100644 index 0000000000..c2c1adcffc --- /dev/null +++ b/cli/factory.go @@ -0,0 +1,98 @@ +// Copyright (c) 2018 HyperHQ Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package main + +import ( + "errors" + "fmt" + + vc "github.com/kata-containers/runtime/virtcontainers" + vf "github.com/kata-containers/runtime/virtcontainers/factory" + "github.com/kata-containers/runtime/virtcontainers/pkg/oci" + "github.com/urfave/cli" +) + +var factorySubCmds = []cli.Command{ + initFactoryCommand, + destroyFactoryCommand, +} + +var factoryCLICommand = cli.Command{ + Name: "factory", + Usage: "manage vm factory", + Subcommands: factorySubCmds, + Action: func(context *cli.Context) { + cli.ShowSubcommandHelp(context) + }, +} + +var initFactoryCommand = cli.Command{ + Name: "init", + Usage: "initialize a VM factory based on kata-runtime configuration", + Action: func(context *cli.Context) error { + runtimeConfig, ok := context.App.Metadata["runtimeConfig"].(oci.RuntimeConfig) + if !ok { + return errors.New("invalid runtime config") + } + + if runtimeConfig.FactoryConfig.Template { + factoryConfig := vf.Config{ + Template: true, + VMConfig: vc.VMConfig{ + HypervisorType: runtimeConfig.HypervisorType, + HypervisorConfig: runtimeConfig.HypervisorConfig, + AgentType: runtimeConfig.AgentType, + AgentConfig: runtimeConfig.AgentConfig, + }, + } + kataLog.WithField("factory", factoryConfig).Info("create vm factory") + _, err := vf.NewFactory(factoryConfig, false) + if err != nil { + kataLog.WithError(err).Error("create vm factory failed") + return err + } + fmt.Println("vm factory initialized") + } else { + kataLog.Error("vm factory is not enabled") + fmt.Println("vm factory is not enabled") + } + + return nil + }, +} + +var destroyFactoryCommand = cli.Command{ + Name: "destroy", + Usage: "destroy the VM factory", + Action: func(context *cli.Context) error { + runtimeConfig, ok := context.App.Metadata["runtimeConfig"].(oci.RuntimeConfig) + if !ok { + return errors.New("invalid runtime config") + } + + if runtimeConfig.FactoryConfig.Template { + factoryConfig := vf.Config{ + Template: true, + VMConfig: vc.VMConfig{ + HypervisorType: runtimeConfig.HypervisorType, + HypervisorConfig: runtimeConfig.HypervisorConfig, + AgentType: runtimeConfig.AgentType, + AgentConfig: runtimeConfig.AgentConfig, + }, + } + kataLog.WithField("factory", factoryConfig).Info("load vm factory") + f, err := vf.NewFactory(factoryConfig, true) + if err != nil { + kataLog.WithError(err).Error("load vm factory failed") + // ignore error + } else { + f.CloseFactory() + } + } + fmt.Println("vm factory destroyed") + return nil + }, +} diff --git a/cli/factory_test.go b/cli/factory_test.go new file mode 100644 index 0000000000..ccf48856d7 --- /dev/null +++ b/cli/factory_test.go @@ -0,0 +1,117 @@ +// Copyright (c) 2018 HyperHQ Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package main + +import ( + "flag" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" + + vc "github.com/kata-containers/runtime/virtcontainers" +) + +func TestFactoryCLIFunctionNoRuntimeConfig(t *testing.T) { + assert := assert.New(t) + + app := cli.NewApp() + ctx := cli.NewContext(app, nil, nil) + app.Name = "foo" + ctx.App.Metadata = map[string]interface{}{ + "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", "", "") + + app := cli.NewApp() + ctx := cli.NewContext(app, set, nil) + app.Name = "foo" + + // No template + ctx.App.Metadata = map[string]interface{}{ + "runtimeConfig": runtimeConfig, + } + fn, ok := initFactoryCommand.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 + runtimeConfig.AgentType = vc.NoopAgentType + ctx.App.Metadata["runtimeConfig"] = runtimeConfig + fn, ok = initFactoryCommand.Action.(func(context *cli.Context) error) + assert.True(ok) + 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", "", "") + + app := cli.NewApp() + ctx := cli.NewContext(app, set, nil) + app.Name = "foo" + + // No template + ctx.App.Metadata = map[string]interface{}{ + "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 + runtimeConfig.AgentType = vc.NoopAgentType + ctx.App.Metadata["runtimeConfig"] = runtimeConfig + fn, ok = destroyFactoryCommand.Action.(func(context *cli.Context) error) + assert.True(ok) + err = fn(ctx) + assert.Nil(err) +} diff --git a/cli/main.go b/cli/main.go index ed3bcdf4d9..76fbfb172f 100644 --- a/cli/main.go +++ b/cli/main.go @@ -123,6 +123,7 @@ var runtimeCommands = []cli.Command{ // Kata Containers specific extensions kataCheckCLICommand, kataEnvCLICommand, + factoryCLICommand, } // runtimeBeforeSubcommands is the function to run before command-line diff --git a/virtcontainers/pkg/oci/utils.go b/virtcontainers/pkg/oci/utils.go index cc68cfc214..680354b1d7 100644 --- a/virtcontainers/pkg/oci/utils.go +++ b/virtcontainers/pkg/oci/utils.go @@ -91,6 +91,12 @@ type CompatOCISpec struct { Process *CompatOCIProcess `json:"process,omitempty"` } +// FactoryConfig is a structure to set the VM factory configuration. +type FactoryConfig struct { + // Template enables VM templating support in VM factory. + Template bool +} + // RuntimeConfig aggregates all runtime specific settings type RuntimeConfig struct { VMConfig vc.Resources @@ -98,6 +104,8 @@ type RuntimeConfig struct { HypervisorType vc.HypervisorType HypervisorConfig vc.HypervisorConfig + FactoryConfig FactoryConfig + AgentType vc.AgentType AgentConfig interface{}