summaryrefslogtreecommitdiffhomepage
path: root/runsc/config
diff options
context:
space:
mode:
authorFabricio Voznika <fvoznika@google.com>2020-09-01 11:10:15 -0700
committergVisor bot <gvisor-bot@google.com>2020-09-01 11:12:19 -0700
commit71589b7f7e69b85820c9deb75fc6d0b6c5f00eb1 (patch)
tree89811036609d6d6c1ad0da6c32f02024aa15c8b4 /runsc/config
parent0eae08bc9e77d54c415c9d59ac3e1fa1f35f0a18 (diff)
Let flags be overriden from OCI annotations
This allows runsc flags to be set per sandbox instance. For example, K8s pod annotations can be used to enable --debug for a single pod, making troubleshoot much easier. Similarly, features like --vfs2 can be enabled for experimentation without affecting other pods in the node. Closes #3494 PiperOrigin-RevId: 329542815
Diffstat (limited to 'runsc/config')
-rw-r--r--runsc/config/config.go2
-rw-r--r--runsc/config/config_test.go87
-rw-r--r--runsc/config/flags.go36
3 files changed, 125 insertions, 0 deletions
diff --git a/runsc/config/config.go b/runsc/config/config.go
index bca27ebf1..df134bb2f 100644
--- a/runsc/config/config.go
+++ b/runsc/config/config.go
@@ -157,6 +157,8 @@ type Config struct {
// Enables FUSE usage.
FUSE bool `flag:"fuse"`
+ AllowFlagOverride bool `flag:"allow-flag-override"`
+
// TestOnlyAllowRunAsCurrentUserWithoutChroot should only be used in
// tests. It allows runsc to start the sandbox process as the current
// user, and without chrooting the sandbox process. This can be
diff --git a/runsc/config/config_test.go b/runsc/config/config_test.go
index af7867a2a..fb162b7eb 100644
--- a/runsc/config/config_test.go
+++ b/runsc/config/config_test.go
@@ -183,3 +183,90 @@ func TestValidationFail(t *testing.T) {
})
}
}
+
+func TestOverride(t *testing.T) {
+ c, err := NewFromFlags()
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.AllowFlagOverride = true
+
+ t.Run("string", func(t *testing.T) {
+ c.RootDir = "foobar"
+ if err := c.Override("root", "bar"); err != nil {
+ t.Fatalf("Override(root, bar) failed: %v", err)
+ }
+ defer setDefault("root")
+ if c.RootDir != "bar" {
+ t.Errorf("Override(root, bar) didn't work: %+v", c)
+ }
+ })
+
+ t.Run("bool", func(t *testing.T) {
+ c.Debug = true
+ if err := c.Override("debug", "false"); err != nil {
+ t.Fatalf("Override(debug, false) failed: %v", err)
+ }
+ defer setDefault("debug")
+ if c.Debug {
+ t.Errorf("Override(debug, false) didn't work: %+v", c)
+ }
+ })
+
+ t.Run("enum", func(t *testing.T) {
+ c.FileAccess = FileAccessShared
+ if err := c.Override("file-access", "exclusive"); err != nil {
+ t.Fatalf("Override(file-access, exclusive) failed: %v", err)
+ }
+ defer setDefault("file-access")
+ if c.FileAccess != FileAccessExclusive {
+ t.Errorf("Override(file-access, exclusive) didn't work: %+v", c)
+ }
+ })
+}
+
+func TestOverrideDisabled(t *testing.T) {
+ c, err := NewFromFlags()
+ if err != nil {
+ t.Fatal(err)
+ }
+ const errMsg = "flag override disabled"
+ if err := c.Override("root", "path"); err == nil || !strings.Contains(err.Error(), errMsg) {
+ t.Errorf("Override() wrong error: %v", err)
+ }
+}
+
+func TestOverrideError(t *testing.T) {
+ c, err := NewFromFlags()
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.AllowFlagOverride = true
+ for _, tc := range []struct {
+ name string
+ value string
+ error string
+ }{
+ {
+ name: "invalid",
+ value: "valid",
+ error: `flag "invalid" not found`,
+ },
+ {
+ name: "debug",
+ value: "invalid",
+ error: "error setting flag debug",
+ },
+ {
+ name: "file-access",
+ value: "invalid",
+ error: "invalid file access type",
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ if err := c.Override(tc.name, tc.value); err == nil || !strings.Contains(err.Error(), tc.error) {
+ t.Errorf("Override(%q, %q) wrong error: %v", tc.name, tc.value, err)
+ }
+ })
+ }
+}
diff --git a/runsc/config/flags.go b/runsc/config/flags.go
index 488a4b9fb..eff46e938 100644
--- a/runsc/config/flags.go
+++ b/runsc/config/flags.go
@@ -48,6 +48,7 @@ func RegisterFlags() {
flag.Bool("log-packets", false, "enable network packet logging.")
flag.String("debug-log-format", "text", "log format: text (default), json, or json-k8s.")
flag.Bool("alsologtostderr", false, "send log messages to stderr.")
+ flag.Bool("allow-flag-override", false, "allow OCI annotations (dev.gvisor.flag.<name>) to override flags for debugging.")
// Debugging flags: strace related
flag.Bool("strace", false, "enable strace.")
@@ -149,6 +150,41 @@ func (c *Config) ToFlags() []string {
return rv
}
+// Override writes a new value to a flag.
+func (c *Config) Override(name string, value string) error {
+ if !c.AllowFlagOverride {
+ return fmt.Errorf("flag override disabled, use --allow-flag-override to enable it")
+ }
+
+ obj := reflect.ValueOf(c).Elem()
+ st := obj.Type()
+ for i := 0; i < st.NumField(); i++ {
+ f := st.Field(i)
+ fieldName, ok := f.Tag.Lookup("flag")
+ if !ok || fieldName != name {
+ // Not a flag field, or flag name doesn't match.
+ continue
+ }
+ fl := flag.CommandLine.Lookup(name)
+ if fl == nil {
+ // Flag must exist if there is a field match above.
+ panic(fmt.Sprintf("Flag %q not found", name))
+ }
+
+ // Use flag to convert the string value to the underlying flag type, using
+ // the same rules as the command-line for consistency.
+ if err := fl.Value.Set(value); err != nil {
+ return fmt.Errorf("error setting flag %s=%q: %w", name, value, err)
+ }
+ x := reflect.ValueOf(flag.Get(fl.Value))
+ obj.Field(i).Set(x)
+
+ // Validates the config again to ensure it's left in a consistent state.
+ return c.validate()
+ }
+ return fmt.Errorf("flag %q not found. Cannot set it to %q", name, value)
+}
+
func getVal(field reflect.Value) string {
if str, ok := field.Addr().Interface().(fmt.Stringer); ok {
return str.String()