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 }