diff --git a/docs/crio.conf.5.md b/docs/crio.conf.5.md index 42911b7b0a1..433bf71c50d 100644 --- a/docs/crio.conf.5.md +++ b/docs/crio.conf.5.md @@ -101,7 +101,7 @@ The `crio.api` table contains settings for the kubelet/gRPC interface. The `crio.runtime` table contains settings pertaining to the OCI runtime used and options for how to set up and manage the OCI runtime. **default_runtime**="runc" - The _name_ of the OCI runtime to be used as the default. + The _name_ of the OCI runtime to be used as the default. This option supports live configuration reload. **default_ulimits**=[] A list of ulimits to be set in containers by default, specified as "=:", for example:"nofile=1024:2048". If nothing is set here, settings will be inherited from the CRI-O daemon. @@ -291,7 +291,7 @@ the container runtime configuration. Enable CRIU integration, requires that the criu binary is available in $PATH. (default: false) ### CRIO.RUNTIME.RUNTIMES TABLE -The "crio.runtime.runtimes" table defines a list of OCI compatible runtimes. The runtime to use is picked based on the runtime handler provided by the CRI. If no runtime handler is provided, the runtime will be picked based on the level of trust of the workload. +The "crio.runtime.runtimes" table defines a list of OCI compatible runtimes. The runtime to use is picked based on the runtime handler provided by the CRI. If no runtime handler is provided, the runtime will be picked based on the level of trust of the workload. This option supports live configuration reload. This option supports live configuration reload. **runtime_path**="" Path to the OCI compatible runtime used for this runtime handler. diff --git a/pkg/config/config.go b/pkg/config/config.go index ed1eaf2aaac..6f954bf9c2d 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -963,21 +963,8 @@ func (c *RuntimeConfig) Validate(systemContext *types.SystemContext, onExecution return err } - // check we do have at least a runtime - if _, ok := c.Runtimes[c.DefaultRuntime]; !ok { - // Set the default runtime to "runc" if default_runtime is not set - if c.DefaultRuntime == "" { - logrus.Debugf("Defaulting to %q as the runtime since default_runtime is not set", defaultRuntime) - // The default config sets runc and its path in the runtimes map, so check for that - // first. If it does not exist then we add runc + its path to the runtimes map. - if _, ok := c.Runtimes[defaultRuntime]; !ok { - c.Runtimes[defaultRuntime] = defaultRuntimeHandler() - } - // Set the DefaultRuntime to runc so we don't fail further along in the code - c.DefaultRuntime = defaultRuntime - } else { - return fmt.Errorf("default_runtime set to %q, but no runtime entry table [crio.runtime.runtimes.%s] was found", c.DefaultRuntime, c.DefaultRuntime) - } + if err := c.ValidateDefaultRuntime(); err != nil { + return err } if c.LogSizeMax >= 0 && c.LogSizeMax < OCIBufSize { @@ -1108,6 +1095,31 @@ func (c *RuntimeConfig) Validate(systemContext *types.SystemContext, onExecution return nil } +// ValidateDefaultRuntime ensures that the default runtime is set and valid. +func (c *RuntimeConfig) ValidateDefaultRuntime() error { + // If the default runtime is defined in the runtime entry table, then it is valid + if _, ok := c.Runtimes[c.DefaultRuntime]; ok { + return nil + } + + // If a non-empty runtime does not exist in the runtime entry table, this is an error. + if c.DefaultRuntime != "" { + return fmt.Errorf("default_runtime set to %q, but no runtime entry table [crio.runtime.runtimes.%s] was found", c.DefaultRuntime, c.DefaultRuntime) + } + + // Set the default runtime to "runc" if default_runtime is not set + logrus.Debugf("Defaulting to %q as the runtime since default_runtime is not set", defaultRuntime) + // The default config sets runc and its path in the runtimes map, so check for that + // first. If it does not exist then we add runc + its path to the runtimes map. + if _, ok := c.Runtimes[defaultRuntime]; !ok { + c.Runtimes[defaultRuntime] = defaultRuntimeHandler() + } + // Set the DefaultRuntime to runc so we don't fail further along in the code + c.DefaultRuntime = defaultRuntime + + return nil +} + func defaultRuntimeHandler() *RuntimeHandler { return &RuntimeHandler{ RuntimeType: DefaultRuntimeType, diff --git a/pkg/config/reload.go b/pkg/config/reload.go index 196590bdbaa..7520641871f 100644 --- a/pkg/config/reload.go +++ b/pkg/config/reload.go @@ -88,6 +88,9 @@ func (c *Config) Reload() error { if err := c.ReloadRdtConfig(newConfig); err != nil { return err } + if err := c.ReloadRuntimes(newConfig); err != nil { + return err + } cdi.GetRegistry(cdi.WithSpecDirs(newConfig.CDISpecDirs...)) return nil @@ -237,3 +240,32 @@ func (c *Config) ReloadRdtConfig(newConfig *Config) error { } return nil } + +// ReloadRuntimes reloads the runtimes configuration if changed +func (c *Config) ReloadRuntimes(newConfig *Config) error { + var updated bool + if !RuntimesEqual(c.Runtimes, newConfig.Runtimes) { + logrus.Infof("Updating runtime configuration") + c.Runtimes = newConfig.Runtimes + updated = true + } + + if c.DefaultRuntime != newConfig.DefaultRuntime { + c.DefaultRuntime = newConfig.DefaultRuntime + if err := c.ValidateDefaultRuntime(); err != nil { + return fmt.Errorf("unable to reload runtimes: %w", err) + } + logConfig("default_runtime", c.DefaultRuntime) + updated = true + } + + if !updated { + return nil + } + + if err := c.ValidateRuntimes(); err != nil { + return fmt.Errorf("unabled to reload runtimes: %w", err) + } + + return nil +} diff --git a/pkg/config/reload_test.go b/pkg/config/reload_test.go index e8e7de16724..292be07b5bb 100644 --- a/pkg/config/reload_test.go +++ b/pkg/config/reload_test.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/containers/common/pkg/apparmor" + "github.com/cri-o/cri-o/pkg/config" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -325,4 +326,86 @@ var _ = t.Describe("Config", func() { Expect(sut.ApparmorProfile).To(Equal(profile)) }) }) + + t.Describe("ReloadRuntimes", func() { + It("should succeed without any config change", func() { + // Given + // When + err := sut.ReloadRuntimes(sut) + + // Then + Expect(err).To(BeNil()) + }) + + It("should fail for invalid default_runtime", func() { + // Given + newConfig := &config.Config{} + newConfig.DefaultRuntime = "invalid" + + // When + err := sut.ReloadRuntimes(newConfig) + + // Then + Expect(err).NotTo(BeNil()) + }) + + It("should add a new runtime", func() { + // Given + newRuntimeHandler := &config.RuntimeHandler{ + RuntimePath: "/usr/bin/runc", + PrivilegedWithoutHostDevices: true, + } + newConfig := &config.Config{} + newConfig.Runtimes = make(config.Runtimes) + newConfig.Runtimes["new"] = newRuntimeHandler + + // When + err := sut.ReloadRuntimes(newConfig) + + // Then + Expect(err).To(BeNil()) + Expect(sut.Runtimes).To(HaveKeyWithValue("new", newRuntimeHandler)) + }) + + It("should change the default runtime", func() { + // Given + sut.Runtimes["existing"] = &config.RuntimeHandler{ + RuntimePath: "/usr/bin/runc", + } + newConfig := &config.Config{} + newConfig.Runtimes = sut.Runtimes + newConfig.DefaultRuntime = "existing" + + // When + err := sut.ReloadRuntimes(newConfig) + + // Then + Expect(err).To(BeNil()) + Expect(sut.DefaultRuntime).To(Equal("existing")) + }) + + It("should overwrite existing runtime", func() { + // Given + existingRuntime := &config.RuntimeHandler{ + RuntimePath: "/usr/bin/runc", + } + sut.Runtimes["existing"] = existingRuntime + + newRuntime := &config.RuntimeHandler{ + RuntimePath: "/usr/bin/runc", + PrivilegedWithoutHostDevices: true, + } + newConfig := &config.Config{} + newConfig.Runtimes = make(config.Runtimes) + newConfig.Runtimes["existing"] = newRuntime + + // When + err := sut.ReloadRuntimes(newConfig) + + // Then + Expect(err).To(BeNil()) + Expect(sut.Runtimes).To(HaveKeyWithValue("existing", newRuntime)) + Expect(sut.Runtimes["existing"].PrivilegedWithoutHostDevices).To(BeTrue()) + }) + }) }) diff --git a/test/reload_config.bats b/test/reload_config.bats index 4dac607ad47..ea360b194e7 100644 --- a/test/reload_config.bats +++ b/test/reload_config.bats @@ -13,6 +13,7 @@ function setup() { } function teardown() { + rm -f "$CRIO_CONFIG_DIR/00-new*Runtime.conf" cleanup_test } @@ -215,3 +216,17 @@ function expect_log_failure() { # then expect_log_failure "unable to reload apparmor_profile" } + +@test "reload config should add new runtime" { + # given + cat << EOF > "$CRIO_CONFIG_DIR/00-newRuntime.conf" +[crio.runtime.runtimes.new] +runtime_path = "$RUNTIME_BINARY_PATH" +EOF + + # when + reload_crio + + #then + wait_for_log '"updating runtime configuration"' +}