github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/gadget/install/install_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  // +build !nosecboot
     3  
     4  /*
     5   * Copyright (C) 2019 Canonical Ltd
     6   *
     7   * This program is free software: you can redistribute it and/or modify
     8   * it under the terms of the GNU General Public License version 3 as
     9   * published by the Free Software Foundation.
    10   *
    11   * This program is distributed in the hope that it will be useful,
    12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14   * GNU General Public License for more details.
    15   *
    16   * You should have received a copy of the GNU General Public License
    17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18   *
    19   */
    20  
    21  package install_test
    22  
    23  import (
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"testing"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/dirs"
    32  	"github.com/snapcore/snapd/gadget"
    33  	"github.com/snapcore/snapd/gadget/install"
    34  	"github.com/snapcore/snapd/testutil"
    35  )
    36  
    37  func TestInstall(t *testing.T) { TestingT(t) }
    38  
    39  type installSuite struct {
    40  	testutil.BaseTest
    41  
    42  	dir string
    43  }
    44  
    45  var _ = Suite(&installSuite{})
    46  
    47  // XXX: write a very high level integration like test here that
    48  // mocks the world (sfdisk,lsblk,mkfs,...)? probably silly as
    49  // each part inside bootstrap is tested and we have a spread test
    50  
    51  func (s *installSuite) SetUpTest(c *C) {
    52  	s.BaseTest.SetUpTest(c)
    53  
    54  	s.dir = c.MkDir()
    55  	dirs.SetRootDir(s.dir)
    56  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    57  }
    58  
    59  func (s *installSuite) TestInstallRunError(c *C) {
    60  	err := install.Run("", "", install.Options{}, nil)
    61  	c.Assert(err, ErrorMatches, "cannot use empty gadget root directory")
    62  }
    63  
    64  const mockGadgetYaml = `volumes:
    65    pc:
    66      bootloader: grub
    67      structure:
    68        - name: mbr
    69          type: mbr
    70          size: 440
    71        - name: BIOS Boot
    72          type: DA,21686148-6449-6E6F-744E-656564454649
    73          size: 1M
    74          offset: 1M
    75          offset-write: mbr+92
    76  `
    77  
    78  const mockExtraStructure = `
    79        - name: Writable
    80          role: system-data
    81          filesystem-label: writable
    82          filesystem: ext4
    83          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
    84          size: 1200M
    85  `
    86  
    87  var mockDeviceLayout = gadget.OnDiskVolume{
    88  	Structure: []gadget.OnDiskStructure{
    89  		{
    90  			LaidOutStructure: gadget.LaidOutStructure{
    91  				VolumeStructure: &gadget.VolumeStructure{
    92  					Name: "mbr",
    93  					Size: 440,
    94  				},
    95  				StartOffset: 0,
    96  			},
    97  			Node: "/dev/node1",
    98  		},
    99  		{
   100  			LaidOutStructure: gadget.LaidOutStructure{
   101  				VolumeStructure: &gadget.VolumeStructure{
   102  					Name: "BIOS Boot",
   103  					Size: 1 * gadget.SizeMiB,
   104  				},
   105  				StartOffset: 1 * gadget.SizeMiB,
   106  			},
   107  			Node: "/dev/node2",
   108  		},
   109  	},
   110  	ID:         "anything",
   111  	Device:     "/dev/node",
   112  	Schema:     "gpt",
   113  	Size:       2 * gadget.SizeGiB,
   114  	SectorSize: 512,
   115  }
   116  
   117  func (s *installSuite) TestLayoutCompatibility(c *C) {
   118  	// same contents (the locally created structure should be ignored)
   119  	gadgetLayout := layoutFromYaml(c, mockGadgetYaml)
   120  	err := install.EnsureLayoutCompatibility(gadgetLayout, &mockDeviceLayout)
   121  	c.Assert(err, IsNil)
   122  
   123  	// missing structure (that's ok)
   124  	gadgetLayoutWithExtras := layoutFromYaml(c, mockGadgetYaml+mockExtraStructure)
   125  	err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &mockDeviceLayout)
   126  	c.Assert(err, IsNil)
   127  
   128  	deviceLayoutWithExtras := mockDeviceLayout
   129  	deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure,
   130  		gadget.OnDiskStructure{
   131  			LaidOutStructure: gadget.LaidOutStructure{
   132  				VolumeStructure: &gadget.VolumeStructure{
   133  					Name:  "Extra partition",
   134  					Size:  10 * gadget.SizeMiB,
   135  					Label: "extra",
   136  				},
   137  				StartOffset: 2 * gadget.SizeMiB,
   138  			},
   139  			Node: "/dev/node3",
   140  		},
   141  	)
   142  	// extra structure (should fail)
   143  	err = install.EnsureLayoutCompatibility(gadgetLayout, &deviceLayoutWithExtras)
   144  	c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node3.* in gadget`)
   145  
   146  	// layout is not compatible if the device is too small
   147  	smallDeviceLayout := mockDeviceLayout
   148  	smallDeviceLayout.Size = 100 * gadget.SizeMiB
   149  	// sanity check
   150  	c.Check(gadgetLayoutWithExtras.Size > smallDeviceLayout.Size, Equals, true)
   151  	err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &smallDeviceLayout)
   152  	c.Assert(err, ErrorMatches, `device /dev/node \(100 MiB\) is too small to fit the requested layout \(1\.17 GiB\)`)
   153  }
   154  
   155  func (s *installSuite) TestMBRLayoutCompatibility(c *C) {
   156  	const mockMBRGadgetYaml = `volumes:
   157    pc:
   158      schema: mbr
   159      bootloader: grub
   160      structure:
   161        - name: mbr
   162          type: mbr
   163          size: 440
   164        - name: BIOS Boot
   165          type: DA,21686148-6449-6E6F-744E-656564454649
   166          size: 1M
   167          offset: 1M
   168          offset-write: mbr+92
   169  `
   170  	var mockMBRDeviceLayout = gadget.OnDiskVolume{
   171  		Structure: []gadget.OnDiskStructure{
   172  			{
   173  				LaidOutStructure: gadget.LaidOutStructure{
   174  					VolumeStructure: &gadget.VolumeStructure{
   175  						// partition names have no
   176  						// meaning in MBR schema
   177  						Name: "other",
   178  						Size: 440,
   179  					},
   180  					StartOffset: 0,
   181  				},
   182  				Node: "/dev/node1",
   183  			},
   184  			{
   185  				LaidOutStructure: gadget.LaidOutStructure{
   186  					VolumeStructure: &gadget.VolumeStructure{
   187  						// partition names have no
   188  						// meaning in MBR schema
   189  						Name: "different BIOS Boot",
   190  						Size: 1 * gadget.SizeMiB,
   191  					},
   192  					StartOffset: 1 * gadget.SizeMiB,
   193  				},
   194  				Node: "/dev/node2",
   195  			},
   196  		},
   197  		ID:         "anything",
   198  		Device:     "/dev/node",
   199  		Schema:     "dos",
   200  		Size:       2 * gadget.SizeGiB,
   201  		SectorSize: 512,
   202  	}
   203  	gadgetLayout := layoutFromYaml(c, mockMBRGadgetYaml)
   204  	err := install.EnsureLayoutCompatibility(gadgetLayout, &mockMBRDeviceLayout)
   205  	c.Assert(err, IsNil)
   206  	// structure is missing from disk
   207  	gadgetLayoutWithExtras := layoutFromYaml(c, mockMBRGadgetYaml+mockExtraStructure)
   208  	err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &mockMBRDeviceLayout)
   209  	c.Assert(err, IsNil)
   210  	// add it now
   211  	deviceLayoutWithExtras := mockMBRDeviceLayout
   212  	deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure,
   213  		gadget.OnDiskStructure{
   214  			LaidOutStructure: gadget.LaidOutStructure{
   215  				VolumeStructure: &gadget.VolumeStructure{
   216  					// name is ignored with MBR schema
   217  					Name:       "Extra partition",
   218  					Size:       1200 * gadget.SizeMiB,
   219  					Label:      "extra",
   220  					Filesystem: "ext4",
   221  					Type:       "83",
   222  				},
   223  				StartOffset: 2 * gadget.SizeMiB,
   224  			},
   225  			Node: "/dev/node3",
   226  		},
   227  	)
   228  	err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayoutWithExtras)
   229  	c.Assert(err, IsNil)
   230  	// add another structure that's not part of the gadget
   231  	deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure,
   232  		gadget.OnDiskStructure{
   233  			LaidOutStructure: gadget.LaidOutStructure{
   234  				VolumeStructure: &gadget.VolumeStructure{
   235  					// name is ignored with MBR schema
   236  					Name: "Extra extra partition",
   237  					Size: 1 * gadget.SizeMiB,
   238  				},
   239  				StartOffset: 1202 * gadget.SizeMiB,
   240  			},
   241  			Node: "/dev/node4",
   242  		},
   243  	)
   244  	err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayoutWithExtras)
   245  	c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node4 .* in gadget`)
   246  }
   247  
   248  func (s *installSuite) TestLayoutCompatibilityWithCreatedPartitions(c *C) {
   249  	gadgetLayoutWithExtras := layoutFromYaml(c, mockGadgetYaml+mockExtraStructure)
   250  	deviceLayout := mockDeviceLayout
   251  	// device matches gadget except for the filesystem type
   252  	deviceLayout.Structure = append(deviceLayout.Structure,
   253  		gadget.OnDiskStructure{
   254  			LaidOutStructure: gadget.LaidOutStructure{
   255  				VolumeStructure: &gadget.VolumeStructure{
   256  					Name:       "Writable",
   257  					Size:       1200 * gadget.SizeMiB,
   258  					Label:      "writable",
   259  					Filesystem: "something_else",
   260  				},
   261  				StartOffset: 2 * gadget.SizeMiB,
   262  			},
   263  			Node:                 "/dev/node3",
   264  			CreatedDuringInstall: true,
   265  		},
   266  	)
   267  	err := install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout)
   268  	c.Assert(err, IsNil)
   269  
   270  	// compare layouts without partitions created at install time (should fail)
   271  	deviceLayout.Structure[len(deviceLayout.Structure)-1].CreatedDuringInstall = false
   272  	err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout)
   273  	c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node3.* in gadget`)
   274  
   275  }
   276  
   277  func (s *installSuite) TestSchemaCompatibility(c *C) {
   278  	gadgetLayout := layoutFromYaml(c, mockGadgetYaml)
   279  	deviceLayout := mockDeviceLayout
   280  
   281  	error_msg := "disk partitioning.* doesn't match gadget.*"
   282  
   283  	for i, tc := range []struct {
   284  		gs string
   285  		ds string
   286  		e  string
   287  	}{
   288  		{"", "dos", error_msg},
   289  		{"", "gpt", ""},
   290  		{"", "xxx", error_msg},
   291  		{"mbr", "dos", ""},
   292  		{"mbr", "gpt", error_msg},
   293  		{"mbr", "xxx", error_msg},
   294  		{"gpt", "dos", error_msg},
   295  		{"gpt", "gpt", ""},
   296  		{"gpt", "xxx", error_msg},
   297  		// XXX: "mbr,gpt" is currently unsupported
   298  		{"mbr,gpt", "dos", error_msg},
   299  		{"mbr,gpt", "gpt", error_msg},
   300  		{"mbr,gpt", "xxx", error_msg},
   301  	} {
   302  		c.Logf("%d: %q %q\n", i, tc.gs, tc.ds)
   303  		gadgetLayout.Volume.Schema = tc.gs
   304  		deviceLayout.Schema = tc.ds
   305  		err := install.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout)
   306  		if tc.e == "" {
   307  			c.Assert(err, IsNil)
   308  		} else {
   309  			c.Assert(err, ErrorMatches, tc.e)
   310  		}
   311  	}
   312  	c.Logf("-----")
   313  }
   314  
   315  func (s *installSuite) TestIDCompatibility(c *C) {
   316  	gadgetLayout := layoutFromYaml(c, mockGadgetYaml)
   317  	deviceLayout := mockDeviceLayout
   318  
   319  	error_msg := "disk ID.* doesn't match gadget volume ID.*"
   320  
   321  	for i, tc := range []struct {
   322  		gid string
   323  		did string
   324  		e   string
   325  	}{
   326  		{"", "", ""},
   327  		{"", "123", ""},
   328  		{"123", "345", error_msg},
   329  		{"123", "123", ""},
   330  	} {
   331  		c.Logf("%d: %q %q\n", i, tc.gid, tc.did)
   332  		gadgetLayout.Volume.ID = tc.gid
   333  		deviceLayout.ID = tc.did
   334  		err := install.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout)
   335  		if tc.e == "" {
   336  			c.Assert(err, IsNil)
   337  		} else {
   338  			c.Assert(err, ErrorMatches, tc.e)
   339  		}
   340  	}
   341  	c.Logf("-----")
   342  }
   343  
   344  func layoutFromYaml(c *C, gadgetYaml string) *gadget.LaidOutVolume {
   345  	gadgetRoot := filepath.Join(c.MkDir(), "gadget")
   346  	err := os.MkdirAll(filepath.Join(gadgetRoot, "meta"), 0755)
   347  	c.Assert(err, IsNil)
   348  	err = ioutil.WriteFile(filepath.Join(gadgetRoot, "meta", "gadget.yaml"), []byte(gadgetYaml), 0644)
   349  	c.Assert(err, IsNil)
   350  	pv, err := gadget.PositionedVolumeFromGadget(gadgetRoot)
   351  	c.Assert(err, IsNil)
   352  	return pv
   353  }
   354  
   355  const mockUC20GadgetYaml = `volumes:
   356    pc:
   357      bootloader: grub
   358      structure:
   359        - name: mbr
   360          type: mbr
   361          size: 440
   362        - name: BIOS Boot
   363          type: DA,21686148-6449-6E6F-744E-656564454649
   364          size: 1M
   365          offset: 1M
   366          offset-write: mbr+92
   367        - name: ubuntu-seed
   368          role: system-seed
   369          filesystem: vfat
   370          # UEFI will boot the ESP partition by default first
   371          type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
   372          size: 1200M
   373        - name: ubuntu-data
   374          role: system-data
   375          filesystem: ext4
   376          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   377          size: 750M
   378  `
   379  
   380  func (s *installSuite) setupMockSysfs(c *C) {
   381  	err := os.MkdirAll(filepath.Join(s.dir, "/dev/disk/by-partlabel"), 0755)
   382  	c.Assert(err, IsNil)
   383  
   384  	err = ioutil.WriteFile(filepath.Join(s.dir, "/dev/fakedevice0p1"), nil, 0644)
   385  	c.Assert(err, IsNil)
   386  	err = os.Symlink("../../fakedevice0p1", filepath.Join(s.dir, "/dev/disk/by-partlabel/ubuntu-seed"))
   387  	c.Assert(err, IsNil)
   388  
   389  	// make parent device
   390  	err = ioutil.WriteFile(filepath.Join(s.dir, "/dev/fakedevice0"), nil, 0644)
   391  	c.Assert(err, IsNil)
   392  	// and fake /sys/block structure
   393  	err = os.MkdirAll(filepath.Join(s.dir, "/sys/block/fakedevice0/fakedevice0p1"), 0755)
   394  	c.Assert(err, IsNil)
   395  }
   396  
   397  func (s *installSuite) TestDeviceFromRoleHappy(c *C) {
   398  	s.setupMockSysfs(c)
   399  	lv := layoutFromYaml(c, mockUC20GadgetYaml)
   400  
   401  	device, err := install.DeviceFromRole(lv, gadget.SystemSeed)
   402  	c.Assert(err, IsNil)
   403  	c.Check(device, Matches, ".*/dev/fakedevice0")
   404  }
   405  
   406  func (s *installSuite) TestDeviceFromRoleErrorNoMatchingSysfs(c *C) {
   407  	// note no sysfs mocking
   408  	lv := layoutFromYaml(c, mockUC20GadgetYaml)
   409  
   410  	_, err := install.DeviceFromRole(lv, gadget.SystemSeed)
   411  	c.Assert(err, ErrorMatches, `cannot find device for role "system-seed": device not found`)
   412  }
   413  
   414  func (s *installSuite) TestDeviceFromRoleErrorNoRole(c *C) {
   415  	s.setupMockSysfs(c)
   416  	lv := layoutFromYaml(c, mockGadgetYaml)
   417  
   418  	_, err := install.DeviceFromRole(lv, gadget.SystemSeed)
   419  	c.Assert(err, ErrorMatches, "cannot find role system-seed in gadget")
   420  }