github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/runsc/boot/fs_test.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package boot
    16  
    17  import (
    18  	"reflect"
    19  	"strings"
    20  	"testing"
    21  
    22  	specs "github.com/opencontainers/runtime-spec/specs-go"
    23  	"github.com/SagerNet/gvisor/runsc/config"
    24  )
    25  
    26  func TestPodMountHintsHappy(t *testing.T) {
    27  	spec := &specs.Spec{
    28  		Annotations: map[string]string{
    29  			MountPrefix + "mount1.source": "foo",
    30  			MountPrefix + "mount1.type":   "tmpfs",
    31  			MountPrefix + "mount1.share":  "pod",
    32  
    33  			MountPrefix + "mount2.source":  "bar",
    34  			MountPrefix + "mount2.type":    "bind",
    35  			MountPrefix + "mount2.share":   "container",
    36  			MountPrefix + "mount2.options": "rw,private",
    37  		},
    38  	}
    39  	podHints, err := newPodMountHints(spec)
    40  	if err != nil {
    41  		t.Fatalf("newPodMountHints failed: %v", err)
    42  	}
    43  
    44  	// Check that fields were set correctly.
    45  	mount1 := podHints.mounts["mount1"]
    46  	if want := "mount1"; want != mount1.name {
    47  		t.Errorf("mount1 name, want: %q, got: %q", want, mount1.name)
    48  	}
    49  	if want := "foo"; want != mount1.mount.Source {
    50  		t.Errorf("mount1 source, want: %q, got: %q", want, mount1.mount.Source)
    51  	}
    52  	if want := "tmpfs"; want != mount1.mount.Type {
    53  		t.Errorf("mount1 type, want: %q, got: %q", want, mount1.mount.Type)
    54  	}
    55  	if want := pod; want != mount1.share {
    56  		t.Errorf("mount1 type, want: %q, got: %q", want, mount1.share)
    57  	}
    58  	if want := []string(nil); !reflect.DeepEqual(want, mount1.mount.Options) {
    59  		t.Errorf("mount1 type, want: %q, got: %q", want, mount1.mount.Options)
    60  	}
    61  
    62  	mount2 := podHints.mounts["mount2"]
    63  	if want := "mount2"; want != mount2.name {
    64  		t.Errorf("mount2 name, want: %q, got: %q", want, mount2.name)
    65  	}
    66  	if want := "bar"; want != mount2.mount.Source {
    67  		t.Errorf("mount2 source, want: %q, got: %q", want, mount2.mount.Source)
    68  	}
    69  	if want := "bind"; want != mount2.mount.Type {
    70  		t.Errorf("mount2 type, want: %q, got: %q", want, mount2.mount.Type)
    71  	}
    72  	if want := container; want != mount2.share {
    73  		t.Errorf("mount2 type, want: %q, got: %q", want, mount2.share)
    74  	}
    75  	if want := []string{"private", "rw"}; !reflect.DeepEqual(want, mount2.mount.Options) {
    76  		t.Errorf("mount2 type, want: %q, got: %q", want, mount2.mount.Options)
    77  	}
    78  }
    79  
    80  func TestPodMountHintsErrors(t *testing.T) {
    81  	for _, tst := range []struct {
    82  		name        string
    83  		annotations map[string]string
    84  		error       string
    85  	}{
    86  		{
    87  			name: "too short",
    88  			annotations: map[string]string{
    89  				MountPrefix + "mount1": "foo",
    90  			},
    91  			error: "invalid mount annotation",
    92  		},
    93  		{
    94  			name: "no name",
    95  			annotations: map[string]string{
    96  				MountPrefix + ".source": "foo",
    97  			},
    98  			error: "invalid mount name",
    99  		},
   100  		{
   101  			name: "missing source",
   102  			annotations: map[string]string{
   103  				MountPrefix + "mount1.type":  "tmpfs",
   104  				MountPrefix + "mount1.share": "pod",
   105  			},
   106  			error: "source field",
   107  		},
   108  		{
   109  			name: "missing type",
   110  			annotations: map[string]string{
   111  				MountPrefix + "mount1.source": "foo",
   112  				MountPrefix + "mount1.share":  "pod",
   113  			},
   114  			error: "type field",
   115  		},
   116  		{
   117  			name: "missing share",
   118  			annotations: map[string]string{
   119  				MountPrefix + "mount1.source": "foo",
   120  				MountPrefix + "mount1.type":   "tmpfs",
   121  			},
   122  			error: "share field",
   123  		},
   124  		{
   125  			name: "invalid field name",
   126  			annotations: map[string]string{
   127  				MountPrefix + "mount1.invalid": "foo",
   128  			},
   129  			error: "invalid mount annotation",
   130  		},
   131  		{
   132  			name: "invalid source",
   133  			annotations: map[string]string{
   134  				MountPrefix + "mount1.source": "",
   135  				MountPrefix + "mount1.type":   "tmpfs",
   136  				MountPrefix + "mount1.share":  "pod",
   137  			},
   138  			error: "source cannot be empty",
   139  		},
   140  		{
   141  			name: "invalid type",
   142  			annotations: map[string]string{
   143  				MountPrefix + "mount1.source": "foo",
   144  				MountPrefix + "mount1.type":   "invalid-type",
   145  				MountPrefix + "mount1.share":  "pod",
   146  			},
   147  			error: "invalid type",
   148  		},
   149  		{
   150  			name: "invalid share",
   151  			annotations: map[string]string{
   152  				MountPrefix + "mount1.source": "foo",
   153  				MountPrefix + "mount1.type":   "tmpfs",
   154  				MountPrefix + "mount1.share":  "invalid-share",
   155  			},
   156  			error: "invalid share",
   157  		},
   158  		{
   159  			name: "invalid options",
   160  			annotations: map[string]string{
   161  				MountPrefix + "mount1.source":  "foo",
   162  				MountPrefix + "mount1.type":    "tmpfs",
   163  				MountPrefix + "mount1.share":   "pod",
   164  				MountPrefix + "mount1.options": "invalid-option",
   165  			},
   166  			error: "unknown mount option",
   167  		},
   168  		{
   169  			name: "duplicate source",
   170  			annotations: map[string]string{
   171  				MountPrefix + "mount1.source": "foo",
   172  				MountPrefix + "mount1.type":   "tmpfs",
   173  				MountPrefix + "mount1.share":  "pod",
   174  
   175  				MountPrefix + "mount2.source": "foo",
   176  				MountPrefix + "mount2.type":   "bind",
   177  				MountPrefix + "mount2.share":  "container",
   178  			},
   179  			error: "have the same mount source",
   180  		},
   181  	} {
   182  		t.Run(tst.name, func(t *testing.T) {
   183  			spec := &specs.Spec{Annotations: tst.annotations}
   184  			podHints, err := newPodMountHints(spec)
   185  			if err == nil || !strings.Contains(err.Error(), tst.error) {
   186  				t.Errorf("newPodMountHints invalid error, want: .*%s.*, got: %v", tst.error, err)
   187  			}
   188  			if podHints != nil {
   189  				t.Errorf("newPodMountHints must return nil on failure: %+v", podHints)
   190  			}
   191  		})
   192  	}
   193  }
   194  
   195  func TestGetMountAccessType(t *testing.T) {
   196  	const source = "foo"
   197  	for _, tst := range []struct {
   198  		name        string
   199  		annotations map[string]string
   200  		want        config.FileAccessType
   201  	}{
   202  		{
   203  			name: "container=exclusive",
   204  			annotations: map[string]string{
   205  				MountPrefix + "mount1.source": source,
   206  				MountPrefix + "mount1.type":   "bind",
   207  				MountPrefix + "mount1.share":  "container",
   208  			},
   209  			want: config.FileAccessExclusive,
   210  		},
   211  		{
   212  			name: "pod=shared",
   213  			annotations: map[string]string{
   214  				MountPrefix + "mount1.source": source,
   215  				MountPrefix + "mount1.type":   "bind",
   216  				MountPrefix + "mount1.share":  "pod",
   217  			},
   218  			want: config.FileAccessShared,
   219  		},
   220  		{
   221  			name: "shared=shared",
   222  			annotations: map[string]string{
   223  				MountPrefix + "mount1.source": source,
   224  				MountPrefix + "mount1.type":   "bind",
   225  				MountPrefix + "mount1.share":  "shared",
   226  			},
   227  			want: config.FileAccessShared,
   228  		},
   229  		{
   230  			name: "default=shared",
   231  			annotations: map[string]string{
   232  				MountPrefix + "mount1.source": source + "mismatch",
   233  				MountPrefix + "mount1.type":   "bind",
   234  				MountPrefix + "mount1.share":  "container",
   235  			},
   236  			want: config.FileAccessShared,
   237  		},
   238  	} {
   239  		t.Run(tst.name, func(t *testing.T) {
   240  			spec := &specs.Spec{Annotations: tst.annotations}
   241  			podHints, err := newPodMountHints(spec)
   242  			if err != nil {
   243  				t.Fatalf("newPodMountHints failed: %v", err)
   244  			}
   245  			mounter := containerMounter{hints: podHints}
   246  			conf := &config.Config{FileAccessMounts: config.FileAccessShared}
   247  			if got := mounter.getMountAccessType(conf, &specs.Mount{Source: source}); got != tst.want {
   248  				t.Errorf("getMountAccessType(), want: %v, got: %v", tst.want, got)
   249  			}
   250  		})
   251  	}
   252  }