github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/libvirttools/persistentroot_volumesource_test.go (about)

     1  /*
     2  Copyright 2018 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package libvirttools
    18  
    19  import (
    20  	"os"
    21  	"path/filepath"
    22  	"strconv"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/Mirantis/virtlet/tests/gm"
    27  	digest "github.com/opencontainers/go-digest"
    28  
    29  	fakeblockdev "github.com/Mirantis/virtlet/pkg/blockdev/fake"
    30  	"github.com/Mirantis/virtlet/pkg/metadata/types"
    31  	fakeutils "github.com/Mirantis/virtlet/pkg/utils/fake"
    32  	testutils "github.com/Mirantis/virtlet/pkg/utils/testing"
    33  	fakevirt "github.com/Mirantis/virtlet/pkg/virt/fake"
    34  )
    35  
    36  func TestPersistentRootVolume(t *testing.T) {
    37  	fakeImages := []fakeImageSpec{
    38  		{
    39  			name:   "persistent/image1",
    40  			path:   "/fake/path1",
    41  			digest: digest.Digest("sha256:12b05d23a781e4aae1ab9a7de27721cbd1f1d666cfb4e21ab31338eb96eb1e3f"),
    42  			size:   8192,
    43  		},
    44  		{
    45  			name:   "persistent/image2",
    46  			path:   "/fake/path2",
    47  			digest: digest.Digest("sha256:d66ab8e0ea2931d41e27ba4f1d9c007a1d43ab883158a8a22f90872a8d9bb0e3"),
    48  			size:   10000,
    49  		},
    50  	}
    51  	for _, tc := range []struct {
    52  		name              string
    53  		imageName         string
    54  		secondImageName   string
    55  		dmPath            string
    56  		fileSize          uint64
    57  		imageWrittenAgain bool
    58  		useSymlink        bool
    59  		errors            [2]string
    60  		annotations       *types.VirtletAnnotations
    61  	}{
    62  		{
    63  			name:            "image unchanged",
    64  			imageName:       "persistent/image1",
    65  			secondImageName: "persistent/image1",
    66  			fileSize:        8704, // just added a sector
    67  		},
    68  		{
    69  			name:              "image change",
    70  			imageName:         "persistent/image1",
    71  			secondImageName:   "persistent/image2",
    72  			fileSize:          16384,
    73  			imageWrittenAgain: true,
    74  		},
    75  		{
    76  			name:      "first image too big",
    77  			imageName: "persistent/image1",
    78  			fileSize:  4096,
    79  			errors: [2]string{
    80  				"too small",
    81  				"",
    82  			},
    83  		},
    84  		{
    85  			name:            "second image too big",
    86  			imageName:       "persistent/image1",
    87  			secondImageName: "persistent/image2",
    88  			fileSize:        8704,
    89  			errors: [2]string{
    90  				"",
    91  				"too small",
    92  			},
    93  		},
    94  		{
    95  			name:            "symlinks",
    96  			imageName:       "persistent/image1",
    97  			secondImageName: "persistent/image1",
    98  			fileSize:        8704,
    99  			useSymlink:      true,
   100  		},
   101  		{
   102  			name:      "files",
   103  			imageName: "persistent/image1",
   104  			fileSize:  8704, // just added a sector
   105  			annotations: &types.VirtletAnnotations{
   106  				InjectedFiles: map[string][]byte{
   107  					"/foo/bar.txt": []byte("bar"),
   108  					"/foo/baz.txt": []byte("baz"),
   109  				},
   110  			},
   111  		},
   112  	} {
   113  		t.Run(tc.name, func(t *testing.T) {
   114  			fakeblockdev.WithFakeRootDev(t, tc.fileSize, func(devPath, devDir string) {
   115  				rec := testutils.NewToplevelRecorder()
   116  				im := newFakeImageManager(rec.Child("image"), fakeImages...)
   117  				if tc.fileSize%512 != 0 {
   118  					t.Fatalf("block device size must be a multiple of 512")
   119  				}
   120  
   121  				devPathToUse := devPath
   122  				if tc.useSymlink {
   123  					devPathToUse = filepath.Join(devDir, "rootdevlink")
   124  					if err := os.Symlink(devPath, devPathToUse); err != nil {
   125  						t.Fatalf("Symlink(): %v", err)
   126  					}
   127  				}
   128  
   129  				for n, imageName := range []string{tc.imageName, tc.secondImageName} {
   130  					if imageName == "" {
   131  						continue
   132  					}
   133  					cmdSpecs := []fakeutils.CmdSpec{
   134  						{
   135  							Match:  "blockdev --getsz",
   136  							Stdout: strconv.Itoa(int(tc.fileSize / 512)),
   137  						},
   138  						{
   139  							Match: "dmsetup create",
   140  						},
   141  						{
   142  							Match: "dmsetup remove",
   143  						},
   144  					}
   145  					if n == 0 || tc.imageWrittenAgain {
   146  						// qemu-img convert is used to write the image to the block device.
   147  						// It should only be called if the image changes.
   148  						cmdSpecs = append(cmdSpecs, fakeutils.CmdSpec{
   149  							Match: "qemu-img convert",
   150  						})
   151  					}
   152  					cmd := fakeutils.NewCommander(rec, cmdSpecs)
   153  					cmd.ReplaceTempPath("__dev__", "/dev")
   154  					owner := newFakeVolumeOwner(fakevirt.NewFakeStorageConnection(rec), nil, im, cmd)
   155  					rootVol := getPersistentRootVolume(t, imageName, devPathToUse, owner, tc.annotations)
   156  					verifyRootVolumeSetup(t, rec, rootVol, tc.errors[n])
   157  					if tc.errors[n] == "" {
   158  						verifyRootVolumeTeardown(t, rec, rootVol)
   159  					}
   160  				}
   161  				gm.Verify(t, gm.NewYamlVerifier(rec.Content()))
   162  			})
   163  		})
   164  	}
   165  }
   166  
   167  func verifyRootVolumeSetup(t *testing.T, rec testutils.Recorder, rootVol *persistentRootVolume, expectedError string) {
   168  	rec.Rec("setup", nil)
   169  	vol, fs, err := rootVol.Setup()
   170  	if expectedError == "" {
   171  		if err != nil {
   172  			t.Fatalf("Setup returned an unexpected error: %v", err)
   173  		}
   174  	} else {
   175  		switch {
   176  		case err == nil:
   177  			t.Errorf("Setup didn't return the expected error")
   178  		case !strings.Contains(err.Error(), expectedError):
   179  			t.Errorf("Setup returned a wrong error message %q (must contain %q)", err, expectedError)
   180  		}
   181  		return
   182  	}
   183  
   184  	if fs != nil {
   185  		t.Errorf("Didn't expect a filesystem")
   186  	}
   187  
   188  	if vol.Source.Block == nil {
   189  		t.Errorf("Expected 'block' volume type")
   190  	}
   191  
   192  	if vol.Device != "disk" {
   193  		t.Errorf("Expected 'disk' as volume device, received: %s", vol.Device)
   194  	}
   195  
   196  	expectedDmPath := "/dev/mapper/virtlet-dm-" + testUUID
   197  	if vol.Source.Block.Dev != expectedDmPath {
   198  		t.Errorf("Expected '%s' as root block device path, received: %s", expectedDmPath, vol.Source.Block.Dev)
   199  	}
   200  
   201  	out, err := vol.Marshal()
   202  	if err != nil {
   203  		t.Fatalf("error marshalling the volume: %v", err)
   204  	}
   205  	rec.Rec("end setup -- root disk", out)
   206  }
   207  
   208  func verifyRootVolumeTeardown(t *testing.T, rec testutils.Recorder, rootVol *persistentRootVolume) {
   209  	rec.Rec("teardown", nil)
   210  	if err := rootVol.Teardown(); err != nil {
   211  		t.Fatalf("Teardown(): %v", err)
   212  	}
   213  	rec.Rec("end teardown", nil)
   214  }
   215  
   216  func getPersistentRootVolume(t *testing.T, imageName, devHostPath string, owner volumeOwner, annotations *types.VirtletAnnotations) *persistentRootVolume {
   217  	if annotations == nil {
   218  		annotations = &types.VirtletAnnotations{}
   219  	}
   220  	volumes, err := GetRootVolume(
   221  		&types.VMConfig{
   222  			DomainUUID: testUUID,
   223  			Image:      imageName,
   224  			VolumeDevices: []types.VMVolumeDevice{
   225  				{
   226  					DevicePath: "/",
   227  					HostPath:   devHostPath,
   228  				},
   229  			},
   230  			ParsedAnnotations: annotations,
   231  		}, owner)
   232  	if err != nil {
   233  		t.Fatalf("GetRootVolume returned an error: %v", err)
   234  	}
   235  
   236  	if len(volumes) != 1 {
   237  		t.Fatalf("GetRootVolumes returned non single number of volumes: %d", len(volumes))
   238  	}
   239  
   240  	return volumes[0].(*persistentRootVolume)
   241  }