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  }