github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/gadget/install/partition_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019-2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package install_test 21 22 import ( 23 "bytes" 24 "fmt" 25 "io/ioutil" 26 "path/filepath" 27 "time" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/gadget" 32 "github.com/snapcore/snapd/gadget/install" 33 "github.com/snapcore/snapd/testutil" 34 ) 35 36 type partitionTestSuite struct { 37 testutil.BaseTest 38 39 dir string 40 } 41 42 var _ = Suite(&partitionTestSuite{}) 43 44 func (s *partitionTestSuite) SetUpTest(c *C) { 45 s.BaseTest.SetUpTest(c) 46 47 s.dir = c.MkDir() 48 } 49 50 const ( 51 scriptPartitionsBios = iota 52 scriptPartitionsBiosSeed 53 scriptPartitionsBiosSeedData 54 ) 55 56 func makeSfdiskScript(num int) string { 57 var b bytes.Buffer 58 59 b.WriteString(` 60 >&2 echo "Some warning from sfdisk" 61 echo '{ 62 "partitiontable": { 63 "label": "gpt", 64 "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA", 65 "device": "/dev/node", 66 "unit": "sectors", 67 "firstlba": 34, 68 "lastlba": 8388574, 69 "partitions": [`) 70 71 // BIOS boot partition 72 if num >= scriptPartitionsBios { 73 b.WriteString(` 74 { 75 "node": "/dev/node1", 76 "start": 2048, 77 "size": 2048, 78 "type": "21686148-6449-6E6F-744E-656564454649", 79 "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F", 80 "name": "BIOS Boot" 81 }`) 82 } 83 84 // Seed partition 85 if num >= scriptPartitionsBiosSeed { 86 b.WriteString(`, 87 { 88 "node": "/dev/node2", 89 "start": 4096, 90 "size": 2457600, 91 "type": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B", 92 "uuid": "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F", 93 "name": "Recovery", 94 "attrs": "GUID:59" 95 }`) 96 } 97 98 // Data partition 99 if num >= scriptPartitionsBiosSeedData { 100 b.WriteString(`, 101 { 102 "node": "/dev/node3", 103 "start": 2461696, 104 "size": 2457600, 105 "type": "0FC63DAF-8483-4772-8E79-3D69D8477DE4", 106 "uuid": "f940029d-bfbb-4887-9d44-321e85c63866", 107 "name": "Writable", 108 "attrs": "GUID:59" 109 }`) 110 } 111 112 b.WriteString(` 113 ] 114 } 115 }'`) 116 return b.String() 117 } 118 119 func makeLsblkScript(num int) string { 120 var b bytes.Buffer 121 122 // BIOS boot partition 123 if num >= scriptPartitionsBios { 124 b.WriteString(` 125 [ "$3" == "/dev/node1" ] && echo '{ 126 "blockdevices": [ {"name": "node1", "fstype": null, "label": null, "uuid": null, "mountpoint": null} ] 127 }'`) 128 } 129 130 // Seed partition 131 if num >= scriptPartitionsBiosSeed { 132 b.WriteString(` 133 [ "$3" == "/dev/node2" ] && echo '{ 134 "blockdevices": [ {"name": "node2", "fstype": "vfat", "label": "ubuntu-seed", "uuid": "A644-B807", "mountpoint": null} ] 135 }'`) 136 } 137 138 // Data partition 139 if num >= scriptPartitionsBiosSeedData { 140 b.WriteString(` 141 [ "$3" == "/dev/node3" ] && echo '{ 142 "blockdevices": [ {"name": "node3", "fstype": "ext4", "label": "ubuntu-data", "uuid": "8781-433a", "mountpoint": null} ] 143 }'`) 144 } 145 146 b.WriteString(` 147 exit 0`) 148 149 return b.String() 150 } 151 152 var mockOnDiskStructureWritable = gadget.OnDiskStructure{ 153 Node: "/dev/node3", 154 CreatedDuringInstall: true, 155 LaidOutStructure: gadget.LaidOutStructure{ 156 VolumeStructure: &gadget.VolumeStructure{ 157 Name: "Writable", 158 Size: 1258291200, 159 Type: "83,0FC63DAF-8483-4772-8E79-3D69D8477DE4", 160 Role: "system-data", 161 Label: "ubuntu-data", 162 Filesystem: "ext4", 163 }, 164 StartOffset: 1260388352, 165 Index: 3, 166 }, 167 } 168 169 func (s *partitionTestSuite) TestCreatePartitions(c *C) { 170 cmdSfdisk := testutil.MockCommand(c, "sfdisk", makeSfdiskScript(scriptPartitionsBiosSeed)) 171 defer cmdSfdisk.Restore() 172 173 cmdLsblk := testutil.MockCommand(c, "lsblk", makeLsblkScript(scriptPartitionsBiosSeed)) 174 defer cmdLsblk.Restore() 175 176 cmdPartx := testutil.MockCommand(c, "partx", "") 177 defer cmdPartx.Restore() 178 179 calls := 0 180 restore := install.MockEnsureNodesExist(func(ds []gadget.OnDiskStructure, timeout time.Duration) error { 181 calls++ 182 c.Assert(ds, HasLen, 1) 183 c.Assert(ds[0].Node, Equals, "/dev/node3") 184 return nil 185 }) 186 defer restore() 187 188 gadgetRoot := filepath.Join(c.MkDir(), "gadget") 189 err := makeMockGadget(gadgetRoot, gadgetContent) 190 c.Assert(err, IsNil) 191 pv, err := gadget.PositionedVolumeFromGadget(gadgetRoot) 192 c.Assert(err, IsNil) 193 194 dl, err := gadget.OnDiskVolumeFromDevice("/dev/node") 195 c.Assert(err, IsNil) 196 created, err := install.CreateMissingPartitions(dl, pv) 197 c.Assert(err, IsNil) 198 c.Assert(created, DeepEquals, []gadget.OnDiskStructure{mockOnDiskStructureWritable}) 199 c.Assert(calls, Equals, 1) 200 201 // Check partition table read and write 202 c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{ 203 {"sfdisk", "--json", "-d", "/dev/node"}, 204 {"sfdisk", "--append", "--no-reread", "/dev/node"}, 205 }) 206 207 // Check partition table update 208 c.Assert(cmdPartx.Calls(), DeepEquals, [][]string{ 209 {"partx", "-u", "/dev/node"}, 210 }) 211 } 212 213 func (s *partitionTestSuite) TestRemovePartitionsTrivial(c *C) { 214 // no locally created partitions 215 cmdSfdisk := testutil.MockCommand(c, "sfdisk", makeSfdiskScript(scriptPartitionsBios)) 216 defer cmdSfdisk.Restore() 217 218 cmdLsblk := testutil.MockCommand(c, "lsblk", makeLsblkScript(scriptPartitionsBios)) 219 defer cmdLsblk.Restore() 220 221 cmdPartx := testutil.MockCommand(c, "partx", "") 222 defer cmdPartx.Restore() 223 224 dl, err := gadget.OnDiskVolumeFromDevice("/dev/node") 225 c.Assert(err, IsNil) 226 227 err = install.RemoveCreatedPartitions(dl) 228 c.Assert(err, IsNil) 229 230 c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{ 231 {"sfdisk", "--json", "-d", "/dev/node"}, 232 }) 233 } 234 235 func (s *partitionTestSuite) TestRemovePartitions(c *C) { 236 const mockSfdiskScriptRemovablePartition = ` 237 if [ -f %[1]s/2 ]; then 238 rm %[1]s/[0-9] 239 elif [ -f %[1]s/1 ]; then 240 touch %[1]s/2 241 exit 0 242 else 243 PART=',{"node": "/dev/node2", "start": 4096, "size": 2457600, "type": "0FC63DAF-8483-4772-8E79-3D69D8477DE4", "uuid": "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F", "name": "Recovery", "attrs": "GUID:59"}' 244 touch %[1]s/1 245 fi 246 echo '{ 247 "partitiontable": { 248 "label": "gpt", 249 "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA", 250 "device": "/dev/node", 251 "unit": "sectors", 252 "firstlba": 34, 253 "lastlba": 8388574, 254 "partitions": [ 255 {"node": "/dev/node1", "start": 2048, "size": 2048, "type": "21686148-6449-6E6F-744E-656564454649", "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F", "name": "BIOS Boot"} 256 '"$PART 257 ] 258 } 259 }"` 260 261 cmdSfdisk := testutil.MockCommand(c, "sfdisk", fmt.Sprintf(mockSfdiskScriptRemovablePartition, s.dir)) 262 defer cmdSfdisk.Restore() 263 264 cmdLsblk := testutil.MockCommand(c, "lsblk", makeLsblkScript(scriptPartitionsBiosSeed)) 265 defer cmdLsblk.Restore() 266 267 cmdPartx := testutil.MockCommand(c, "partx", "") 268 defer cmdPartx.Restore() 269 270 dl, err := gadget.OnDiskVolumeFromDevice("/dev/node") 271 c.Assert(err, IsNil) 272 273 c.Assert(cmdLsblk.Calls(), DeepEquals, [][]string{ 274 {"lsblk", "--fs", "--json", "/dev/node1"}, 275 {"lsblk", "--fs", "--json", "/dev/node2"}, 276 }) 277 278 err = install.RemoveCreatedPartitions(dl) 279 c.Assert(err, IsNil) 280 281 c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{ 282 {"sfdisk", "--json", "-d", "/dev/node"}, 283 {"sfdisk", "--no-reread", "--delete", "/dev/node", "2"}, 284 {"sfdisk", "--json", "-d", "/dev/node"}, 285 }) 286 } 287 288 func (s *partitionTestSuite) TestRemovePartitionsError(c *C) { 289 cmdSfdisk := testutil.MockCommand(c, "sfdisk", makeSfdiskScript(scriptPartitionsBiosSeedData)) 290 defer cmdSfdisk.Restore() 291 292 cmdLsblk := testutil.MockCommand(c, "lsblk", makeLsblkScript(scriptPartitionsBiosSeedData)) 293 defer cmdLsblk.Restore() 294 295 cmdPartx := testutil.MockCommand(c, "partx", "") 296 defer cmdPartx.Restore() 297 298 dl, err := gadget.OnDiskVolumeFromDevice("node") 299 c.Assert(err, IsNil) 300 301 err = install.RemoveCreatedPartitions(dl) 302 c.Assert(err, ErrorMatches, "cannot remove partitions: /dev/node3") 303 } 304 305 func (s *partitionTestSuite) TestEnsureNodesExist(c *C) { 306 const mockUdevadmScript = `err=%q; echo "$err"; [ -n "$err" ] && exit 1 || exit 0` 307 for _, tc := range []struct { 308 utErr string 309 err string 310 }{ 311 {utErr: "", err: ""}, 312 {utErr: "some error", err: "some error"}, 313 } { 314 c.Logf("utErr:%q err:%q", tc.utErr, tc.err) 315 316 node := filepath.Join(c.MkDir(), "node") 317 err := ioutil.WriteFile(node, nil, 0644) 318 c.Assert(err, IsNil) 319 320 cmdUdevadm := testutil.MockCommand(c, "udevadm", fmt.Sprintf(mockUdevadmScript, tc.utErr)) 321 defer cmdUdevadm.Restore() 322 323 ds := []gadget.OnDiskStructure{{Node: node}} 324 err = install.EnsureNodesExist(ds, 10*time.Millisecond) 325 if tc.err == "" { 326 c.Assert(err, IsNil) 327 } else { 328 c.Assert(err, ErrorMatches, tc.err) 329 } 330 331 c.Assert(cmdUdevadm.Calls(), DeepEquals, [][]string{ 332 {"udevadm", "trigger", "--settle", node}, 333 }) 334 } 335 } 336 337 func (s *partitionTestSuite) TestEnsureNodesExistTimeout(c *C) { 338 cmdUdevadm := testutil.MockCommand(c, "udevadm", "") 339 defer cmdUdevadm.Restore() 340 341 node := filepath.Join(c.MkDir(), "node") 342 ds := []gadget.OnDiskStructure{{Node: node}} 343 t := time.Now() 344 timeout := 1 * time.Second 345 err := install.EnsureNodesExist(ds, timeout) 346 c.Assert(err, ErrorMatches, fmt.Sprintf("device %s not available", node)) 347 c.Assert(time.Since(t) >= timeout, Equals, true) 348 c.Assert(cmdUdevadm.Calls(), HasLen, 0) 349 }