github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/tests/rkt_volume_mount_test.go (about)

     1  // Copyright 2016 The rkt 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  // +build coreos kvm host fly
    16  
    17  package main
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path"
    23  	"syscall"
    24  	"testing"
    25  
    26  	"github.com/appc/spec/schema"
    27  	"github.com/appc/spec/schema/types"
    28  	"github.com/rkt/rkt/tests/testutils"
    29  )
    30  
    31  // TODO: unite these tests with rkt_run_pod_manifest_test.go
    32  
    33  var (
    34  	boolTrue  = true
    35  	boolFalse = false
    36  
    37  	mountName     types.ACName = "mnt"
    38  	mountDir                   = "/mnt"
    39  	mountFilePath              = "/mnt/subDirRW/file"
    40  
    41  	volDir      = mustTempDir("rkt-tests-volume-data")
    42  	volSubDirRW = path.Join(volDir, "subDirRW")
    43  	volFilePath = path.Join(volSubDirRW, "file")
    44  
    45  	innerFileContent = "inner"
    46  	outerFileContent = "outer"
    47  )
    48  
    49  func prepareTmpDirWithRecursiveMountsAndFiles(t *testing.T) []func() {
    50  	cleanupFuncs := make([]func(), 0)
    51  
    52  	// create directories on the host
    53  	if err := os.MkdirAll(volSubDirRW, 0); err != nil {
    54  		t.Fatalf("Can't create directory %q: %v", volSubDirRW, err)
    55  	}
    56  	cleanupFuncs = append(cleanupFuncs, func() { os.RemoveAll(volDir) })
    57  
    58  	// create the file in subDirRW before the mount
    59  	tmpdir2outerfile, err := os.Create(volFilePath)
    60  	if err != nil {
    61  		executeFuncsReverse(cleanupFuncs)
    62  		t.Fatalf("Can't create outer file %q: %v", volSubDirRW, err)
    63  	}
    64  	cleanupFuncs = append(cleanupFuncs, func() { tmpdir2outerfile.Close() })
    65  
    66  	if _, err := tmpdir2outerfile.WriteString(outerFileContent); err != nil {
    67  		executeFuncsReverse(cleanupFuncs)
    68  		t.Fatalf("Can't write to file %q after mounting: %v", tmpdir2outerfile, err)
    69  	}
    70  
    71  	// mount tmpfs for /dir1/subDirRW
    72  	if err := syscall.Mount("", volSubDirRW, "tmpfs", 0, ""); err != nil {
    73  		executeFuncsReverse(cleanupFuncs)
    74  		t.Fatalf("Can't mount tmpfs on inner temp directory %q: %v", volSubDirRW, err)
    75  	}
    76  	cleanupFuncs = append(cleanupFuncs, func() {
    77  		if err := syscall.Unmount(volSubDirRW, syscall.MNT_DETACH); err != nil {
    78  			t.Errorf("could not unmount %q: %v", volSubDirRW, err)
    79  		}
    80  	})
    81  	cleanupFuncs = append(cleanupFuncs, func() { os.RemoveAll(volDir) })
    82  
    83  	// create the file in subDirRW after the mount
    84  	tmpdir2innerfile, err := os.Create(volFilePath)
    85  	if err != nil {
    86  		executeFuncsReverse(cleanupFuncs)
    87  		t.Fatalf("Can't create inner file %q: %v", volSubDirRW, err)
    88  	}
    89  	cleanupFuncs = append(cleanupFuncs, func() { tmpdir2innerfile.Close() })
    90  
    91  	if _, err := tmpdir2innerfile.WriteString(innerFileContent); err != nil {
    92  		executeFuncsReverse(cleanupFuncs)
    93  		t.Fatalf("Can't write to file %q after mounting: %v", tmpdir2innerfile, err)
    94  	}
    95  
    96  	return cleanupFuncs
    97  }
    98  
    99  type volumeMountTestCase struct {
   100  	description string
   101  	// [image name]:[image patches]
   102  	images         []imagePatch
   103  	cmdArgs        string
   104  	podManifest    *schema.PodManifest
   105  	expectedResult string
   106  	skipPrepared   bool
   107  }
   108  
   109  var (
   110  	volumeMountTestCasesRecursiveCLI = []volumeMountTestCase{
   111  		{
   112  			"CLI: recursive mount read file",
   113  			[]imagePatch{
   114  				{
   115  					"rkt-test-run-read-file.aci",
   116  					[]string{fmt.Sprintf("--exec=/inspect --read-file --file-name %s", mountFilePath)},
   117  				},
   118  			},
   119  			fmt.Sprintf(
   120  				"--volume=test1,kind=host,source=%s,recursive=true --mount volume=test1,target=%s",
   121  				volDir, mountDir,
   122  			),
   123  			nil,
   124  			innerFileContent,
   125  			false,
   126  		},
   127  		{
   128  			"CLI: recursive read-only mount write file must fail",
   129  			[]imagePatch{
   130  				{
   131  					"rkt-test-run-write-file.aci",
   132  					[]string{fmt.Sprintf("--exec=/inspect --write-file --file-name %s", mountFilePath)},
   133  				},
   134  			},
   135  			fmt.Sprintf(
   136  				"--volume=test1,kind=host,source=%s,recursive=true,readOnly=true --mount volume=test1,target=%s",
   137  				volDir, mountDir,
   138  			),
   139  			nil,
   140  			"read-only file system",
   141  			false,
   142  		},
   143  	}
   144  
   145  	volumeMountTestCasesNonRecursiveCLI = []volumeMountTestCase{
   146  		{
   147  			"CLI: read file with non-recursive mount",
   148  			[]imagePatch{
   149  				{
   150  					"rkt-test-run-read-file.aci",
   151  					[]string{fmt.Sprintf("--exec=/inspect --read-file --file-name %s", mountFilePath)},
   152  				},
   153  			},
   154  			fmt.Sprintf(
   155  				"--volume=test,kind=host,source=%s,recursive=false --mount volume=test,target=%s",
   156  				volDir, mountDir,
   157  			),
   158  			nil,
   159  			outerFileContent,
   160  			false,
   161  		},
   162  		{
   163  			"CLI: read-only non-recursive write file must fail",
   164  			[]imagePatch{
   165  				{
   166  					"rkt-test-run-write-file.aci",
   167  					[]string{fmt.Sprintf("--exec=/inspect --write-file --file-name %s", "/mnt/lol")},
   168  				},
   169  			},
   170  			fmt.Sprintf(
   171  				"--volume=test1,kind=host,source=%s,readOnly=true,recursive=false --mount volume=test1,target=%s",
   172  				volDir, mountDir,
   173  			),
   174  			nil,
   175  			"read-only file system",
   176  			false,
   177  		},
   178  	}
   179  
   180  	volumeMountTestCasesRecursivePodManifest = []volumeMountTestCase{
   181  		{
   182  			"Read of nested file for recursive mount",
   183  			[]imagePatch{
   184  				{"rkt-test-run-pod-manifest-recursive-mount-stat.aci", []string{}},
   185  			},
   186  			"",
   187  			&schema.PodManifest{
   188  				Apps: []schema.RuntimeApp{
   189  					{
   190  						Name: baseAppName,
   191  						App: &types.App{
   192  							Exec:  []string{"/inspect", "--read-file"},
   193  							User:  "0",
   194  							Group: "0",
   195  							Environment: []types.EnvironmentVariable{
   196  								{"FILE", mountFilePath},
   197  							},
   198  							MountPoints: []types.MountPoint{
   199  								{mountName, mountDir, false},
   200  							},
   201  						},
   202  					},
   203  				},
   204  				Volumes: []types.Volume{
   205  					{Name: mountName, Kind: "host", Source: volDir,
   206  						ReadOnly: nil, Recursive: &boolTrue,
   207  						Mode: nil, UID: nil, GID: nil},
   208  				},
   209  			},
   210  			innerFileContent,
   211  			false,
   212  		},
   213  		{
   214  			"Write of nested file for recursive/read-only mount must fail",
   215  			[]imagePatch{
   216  				{"rkt-test-run-pod-manifest-recursive-mount-write.aci", []string{}},
   217  			},
   218  			"",
   219  			&schema.PodManifest{
   220  				Apps: []schema.RuntimeApp{
   221  					{
   222  						Name: baseAppName,
   223  						App: &types.App{
   224  							Exec:  []string{"/inspect", "--write-file"},
   225  							User:  "0",
   226  							Group: "0",
   227  							Environment: []types.EnvironmentVariable{
   228  								{"FILE", mountFilePath},
   229  								{"CONTENT", "should-not-see-me"},
   230  							},
   231  							MountPoints: []types.MountPoint{
   232  								{mountName, mountDir, false},
   233  							},
   234  						},
   235  					},
   236  				},
   237  				Volumes: []types.Volume{
   238  					{Name: mountName, Kind: "host", Source: volDir,
   239  						ReadOnly: &boolTrue, Recursive: &boolTrue,
   240  						Mode: nil, UID: nil, GID: nil},
   241  				},
   242  			},
   243  			"read-only file system",
   244  			false,
   245  		},
   246  	}
   247  
   248  	volumeMountTestCasesNonRecursivePodManifest = []volumeMountTestCase{
   249  		{
   250  			"PodManifest: Simple read after write with volume non-recursive mounted in a read-only rootfs.",
   251  			[]imagePatch{
   252  				{"rkt-test-run-pod-manifest-read-only-rootfs-vol-rw.aci", []string{}},
   253  			},
   254  			"",
   255  			&schema.PodManifest{
   256  				Apps: []schema.RuntimeApp{
   257  					{
   258  						Name: baseAppName,
   259  						App: &types.App{
   260  							Exec:  []string{"/inspect", "--write-file", "--read-file"},
   261  							User:  "0",
   262  							Group: "0",
   263  							Environment: []types.EnvironmentVariable{
   264  								{"FILE", path.Join(mountDir, "file")},
   265  								{"CONTENT", "host:foo"},
   266  							},
   267  							MountPoints: []types.MountPoint{
   268  								{mountName, mountDir, false},
   269  							},
   270  						},
   271  						ReadOnlyRootFS: true,
   272  					},
   273  				},
   274  				Volumes: []types.Volume{
   275  					{Name: mountName, Kind: "host", Source: volDir,
   276  						ReadOnly: nil, Recursive: &boolFalse,
   277  						Mode: nil, UID: nil, GID: nil},
   278  				},
   279  			},
   280  			"host:foo",
   281  			false,
   282  		},
   283  	}
   284  
   285  	volumeMountTestCasesNonRecursive = []volumeMountTestCase{
   286  		{
   287  			"Read of nested file for non-recursive mount",
   288  			[]imagePatch{
   289  				{"rkt-test-run-pod-manifest-recursive-mount-stat.aci", []string{}},
   290  			},
   291  			"",
   292  			&schema.PodManifest{
   293  				Apps: []schema.RuntimeApp{
   294  					{
   295  						Name: baseAppName,
   296  						App: &types.App{
   297  							Exec:  []string{"/inspect", "--read-file"},
   298  							User:  "0",
   299  							Group: "0",
   300  							Environment: []types.EnvironmentVariable{
   301  								{"FILE", mountFilePath},
   302  							},
   303  							MountPoints: []types.MountPoint{
   304  								{mountName, mountDir, false},
   305  							},
   306  						},
   307  					},
   308  				},
   309  				Volumes: []types.Volume{
   310  					{Name: mountName, Kind: "host", Source: volDir,
   311  						ReadOnly: nil, Recursive: &boolFalse,
   312  						Mode: nil, UID: nil, GID: nil},
   313  				},
   314  			},
   315  			outerFileContent,
   316  			false,
   317  		},
   318  	}
   319  
   320  	volumeMountTestCasesDuplicateVolume = []volumeMountTestCase{
   321  		{
   322  			"CLI: duplicate volume name",
   323  			[]imagePatch{
   324  				{"rkt-test-run-pod-manifest-duplicate-mount.aci", []string{}},
   325  			},
   326  			fmt.Sprintf(
   327  				"--volume=test,kind=host,source=%s --mount volume=test,target=%s --volume=test,kind=empty --mount volume=test,target=%s",
   328  				volDir, mountDir, path.Join(mountDir, "dup"),
   329  			),
   330  			nil,
   331  			"duplicate volume name",
   332  			true,
   333  		},
   334  	}
   335  )
   336  
   337  func NewTestVolumeMount(volumeMountTestCases [][]volumeMountTestCase) testutils.Test {
   338  	return testutils.TestFunc(func(t *testing.T) {
   339  		ctx := testutils.NewRktRunCtx()
   340  		defer ctx.Cleanup()
   341  
   342  		deferredFuncs := prepareTmpDirWithRecursiveMountsAndFiles(t)
   343  		defer executeFuncsReverse(deferredFuncs)
   344  
   345  		for _, testCases := range volumeMountTestCases {
   346  			for i, tt := range testCases {
   347  				var hashesToRemove []string
   348  				for j, v := range tt.images {
   349  					hash, err := patchImportAndFetchHash(v.name, v.patches, t, ctx)
   350  					if err != nil {
   351  						t.Fatalf("error running patchImportAndFetchHash: %v", err)
   352  					}
   353  
   354  					hashesToRemove = append(hashesToRemove, hash)
   355  					if tt.podManifest != nil {
   356  						imgName := types.MustACIdentifier(v.name)
   357  						imgID, err := types.NewHash(hash)
   358  						if err != nil {
   359  							t.Fatalf("Cannot generate types.Hash from %v: %v", hash, err)
   360  						}
   361  						tt.podManifest.Apps[j].Image.Name = imgName
   362  						tt.podManifest.Apps[j].Image.ID = *imgID
   363  					}
   364  				}
   365  
   366  				manifestFile := ""
   367  				if tt.podManifest != nil {
   368  					tt.podManifest.ACKind = schema.PodManifestKind
   369  					tt.podManifest.ACVersion = schema.AppContainerVersion
   370  
   371  					manifestFile = generatePodManifestFile(t, tt.podManifest)
   372  					defer os.Remove(manifestFile)
   373  				}
   374  
   375  				// 1. Test 'rkt run'.
   376  				runCmd := fmt.Sprintf("%s run --mds-register=false", ctx.Cmd())
   377  				if manifestFile != "" {
   378  					runCmd += fmt.Sprintf(" --pod-manifest=%s", manifestFile)
   379  				} else {
   380  					// TODO: run the tests for more than just the first image
   381  					runCmd += fmt.Sprintf(" %s %s", tt.cmdArgs, hashesToRemove[0])
   382  				}
   383  				t.Logf("Running 'run' test #%v: %q", i, tt.description)
   384  				child := spawnOrFail(t, runCmd)
   385  				ctx.RegisterChild(child)
   386  
   387  				if tt.expectedResult != "" {
   388  					if _, out, err := expectRegexWithOutput(child, tt.expectedResult); err != nil {
   389  						t.Fatalf("Expected %q but not found: %v\n%s", tt.expectedResult, err, out)
   390  					}
   391  				}
   392  				child.Wait()
   393  				verifyHostFile(t, volDir, "file", i, tt.expectedResult)
   394  
   395  				// 2. Test 'rkt prepare' + 'rkt run-prepared'.
   396  				if tt.skipPrepared {
   397  					continue
   398  				}
   399  				prepareCmd := fmt.Sprintf("%s prepare", ctx.Cmd())
   400  				if manifestFile != "" {
   401  					prepareCmd += fmt.Sprintf(" --pod-manifest=%s", manifestFile)
   402  				} else {
   403  					// TODO: run the tests for more than just the first image
   404  					prepareCmd += fmt.Sprintf(" %s %s", tt.cmdArgs, hashesToRemove[0])
   405  				}
   406  				uuid := runRktAndGetUUID(t, prepareCmd)
   407  
   408  				runPreparedCmd := fmt.Sprintf("%s run-prepared --mds-register=false %s", ctx.Cmd(), uuid)
   409  				t.Logf("Running 'run-prepared' test #%v: %q", i, tt.description)
   410  				child = spawnOrFail(t, runPreparedCmd)
   411  
   412  				if tt.expectedResult != "" {
   413  					if _, out, err := expectRegexWithOutput(child, tt.expectedResult); err != nil {
   414  						t.Fatalf("Expected %q but not found: %v\n%s", tt.expectedResult, err, out)
   415  					}
   416  				}
   417  				child.Wait()
   418  				verifyHostFile(t, volDir, "file", i, tt.expectedResult)
   419  
   420  				// we run the garbage collector and remove the imported images to save
   421  				// space
   422  				runGC(t, ctx)
   423  				for _, h := range hashesToRemove {
   424  					removeFromCas(t, ctx, h)
   425  				}
   426  			}
   427  		}
   428  	})
   429  }