github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/interfaces/builtin/raw_volume_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 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 builtin_test
    21  
    22  import (
    23  	. "gopkg.in/check.v1"
    24  	"strings"
    25  
    26  	"github.com/snapcore/snapd/interfaces"
    27  	"github.com/snapcore/snapd/interfaces/apparmor"
    28  	"github.com/snapcore/snapd/interfaces/builtin"
    29  	"github.com/snapcore/snapd/interfaces/udev"
    30  	"github.com/snapcore/snapd/snap"
    31  	"github.com/snapcore/snapd/snap/snaptest"
    32  	"github.com/snapcore/snapd/testutil"
    33  )
    34  
    35  type rawVolumeInterfaceSuite struct {
    36  	testutil.BaseTest
    37  	iface interfaces.Interface
    38  
    39  	// OS snap
    40  	testSlot1Info *snap.SlotInfo
    41  	testSlot2Info *snap.SlotInfo
    42  	testSlot3Info *snap.SlotInfo
    43  
    44  	// Gadget snap
    45  	testUDev1     *interfaces.ConnectedSlot
    46  	testUDev1Info *snap.SlotInfo
    47  	testUDev2     *interfaces.ConnectedSlot
    48  	testUDev2Info *snap.SlotInfo
    49  	testUDev3     *interfaces.ConnectedSlot
    50  	testUDev3Info *snap.SlotInfo
    51  
    52  	testUDevBadValue1     *interfaces.ConnectedSlot
    53  	testUDevBadValue1Info *snap.SlotInfo
    54  
    55  	// Consuming snap
    56  	testPlugPart1     *interfaces.ConnectedPlug
    57  	testPlugPart1Info *snap.PlugInfo
    58  	testPlugPart2     *interfaces.ConnectedPlug
    59  	testPlugPart2Info *snap.PlugInfo
    60  	testPlugPart3     *interfaces.ConnectedPlug
    61  	testPlugPart3Info *snap.PlugInfo
    62  }
    63  
    64  var _ = Suite(&rawVolumeInterfaceSuite{
    65  	iface: builtin.MustInterface("raw-volume"),
    66  })
    67  
    68  const rawVolumeConsumerYaml = `name: consumer
    69  version: 0
    70  apps:
    71   app:
    72    plugs: [raw-volume]
    73  `
    74  
    75  const rawVolumeCoreYaml = `name: core
    76  version: 0
    77  type: os
    78  slots:
    79    raw-volume:
    80  `
    81  
    82  func (s *rawVolumeInterfaceSuite) SetUpTest(c *C) {
    83  	// Mock for OS snap
    84  	osSnapInfo := snaptest.MockInfo(c, `
    85  name: core
    86  version: 0
    87  type: os
    88  slots:
    89    test-part-1:
    90      interface: raw-volume
    91      path: /dev/vda1
    92    test-part-2:
    93      interface: raw-volume
    94      path: /dev/mmcblk0p1
    95    test-part-3:
    96      interface: raw-volume
    97      path: /dev/i2o/hda1
    98  `, nil)
    99  	s.testSlot1Info = osSnapInfo.Slots["test-part-1"]
   100  	s.testSlot2Info = osSnapInfo.Slots["test-part-2"]
   101  	s.testSlot3Info = osSnapInfo.Slots["test-part-3"]
   102  
   103  	// Mock for Gadget snap
   104  	gadgetSnapInfo := snaptest.MockInfo(c, `
   105  name: some-device
   106  version: 0
   107  type: gadget
   108  slots:
   109    test-udev-1:
   110      interface: raw-volume
   111      path: /dev/vda1
   112    test-udev-2:
   113      interface: raw-volume
   114      path: /dev/mmcblk0p1
   115    test-udev-3:
   116      interface: raw-volume
   117      path: /dev/i2o/hda1
   118    test-udev-bad-value-1:
   119      interface: raw-volume
   120      path: /dev/vda0
   121  `, nil)
   122  	s.testUDev1Info = gadgetSnapInfo.Slots["test-udev-1"]
   123  	s.testUDev1 = interfaces.NewConnectedSlot(s.testUDev1Info, nil, nil)
   124  	s.testUDev2Info = gadgetSnapInfo.Slots["test-udev-2"]
   125  	s.testUDev2 = interfaces.NewConnectedSlot(s.testUDev2Info, nil, nil)
   126  	s.testUDev3Info = gadgetSnapInfo.Slots["test-udev-3"]
   127  	s.testUDev3 = interfaces.NewConnectedSlot(s.testUDev3Info, nil, nil)
   128  	s.testUDevBadValue1Info = gadgetSnapInfo.Slots["test-udev-bad-value-1"]
   129  	s.testUDevBadValue1 = interfaces.NewConnectedSlot(s.testUDevBadValue1Info, nil, nil)
   130  
   131  	// Mock for consumer snaps
   132  	consumingSnapInfo := snaptest.MockInfo(c, `
   133  name: client-snap
   134  version: 0
   135  plugs:
   136    plug-for-part-1:
   137      interface: raw-volume
   138    plug-for-part-2:
   139      interface: raw-volume
   140    plug-for-part-3:
   141      interface: raw-volume
   142  apps:
   143    app-accessing-1-part:
   144      command: foo
   145      plugs:
   146      - plug-for-part-1
   147    app-accessing-2-part:
   148      command: foo
   149      plugs:
   150      - plug-for-part-2
   151    app-accessing-3-part:
   152      command: foo
   153      plugs:
   154      - plug-for-part-3
   155  `, nil)
   156  	s.testPlugPart1Info = consumingSnapInfo.Plugs["plug-for-part-1"]
   157  	s.testPlugPart1 = interfaces.NewConnectedPlug(s.testPlugPart1Info, nil, nil)
   158  	s.testPlugPart2Info = consumingSnapInfo.Plugs["plug-for-part-2"]
   159  	s.testPlugPart2 = interfaces.NewConnectedPlug(s.testPlugPart2Info, nil, nil)
   160  	s.testPlugPart3Info = consumingSnapInfo.Plugs["plug-for-part-3"]
   161  	s.testPlugPart3 = interfaces.NewConnectedPlug(s.testPlugPart3Info, nil, nil)
   162  }
   163  
   164  func (s *rawVolumeInterfaceSuite) TestName(c *C) {
   165  	c.Assert(s.iface.Name(), Equals, "raw-volume")
   166  }
   167  
   168  func (s *rawVolumeInterfaceSuite) TestSanitizeCoreSnapSlot(c *C) {
   169  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testSlot1Info), IsNil)
   170  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testSlot2Info), IsNil)
   171  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testSlot3Info), IsNil)
   172  }
   173  
   174  func (s *rawVolumeInterfaceSuite) TestSanitizeGadgetSnapSlot(c *C) {
   175  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev1Info), IsNil)
   176  }
   177  
   178  func (s *rawVolumeInterfaceSuite) TestSanitizeBadGadgetSnapSlot(c *C) {
   179  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue1Info), ErrorMatches, `slot "some-device:test-udev-bad-value-1" path attribute must be a valid device node`)
   180  }
   181  
   182  func (s *rawVolumeInterfaceSuite) TestSanitizeSlotHappy(c *C) {
   183  	const mockSnapYaml = `name: raw-volume-slot-snap
   184  type: gadget
   185  version: 1.0
   186  slots:
   187    raw-volume:
   188      path: $t
   189  `
   190  
   191  	var testCases = []struct {
   192  		input string
   193  	}{
   194  		{`/dev/hda1`},
   195  		{`/dev/hda63`},
   196  		{`/dev/hdb42`},
   197  		{`/dev/hdt63`},
   198  		{`/dev/sda1`},
   199  		{`/dev/sda15`},
   200  		{`/dev/sdb8`},
   201  		{`/dev/sdc14`},
   202  		{`/dev/sdde10`},
   203  		{`/dev/sdiv15`},
   204  		{`/dev/i2o/hda1`},
   205  		{`/dev/i2o/hda15`},
   206  		{`/dev/i2o/hdb8`},
   207  		{`/dev/i2o/hdc10`},
   208  		{`/dev/i2o/hdde10`},
   209  		{`/dev/i2o/hddx15`},
   210  		{`/dev/mmcblk0p1`},
   211  		{`/dev/mmcblk0p63`},
   212  		{`/dev/mmcblk12p42`},
   213  		{`/dev/mmcblk999p63`},
   214  		{`/dev/nvme0p1`},
   215  		{`/dev/nvme0p63`},
   216  		{`/dev/nvme12p42`},
   217  		{`/dev/nvme99p63`},
   218  		{`/dev/nvme0n1p1`},
   219  		{`/dev/nvme0n1p63`},
   220  		{`/dev/nvme12n34p42`},
   221  		{`/dev/nvme99n63p63`},
   222  		{`/dev/vda1`},
   223  		{`/dev/vda63`},
   224  		{`/dev/vdb42`},
   225  		{`/dev/vdz63`},
   226  	}
   227  
   228  	for _, t := range testCases {
   229  		yml := strings.Replace(mockSnapYaml, "$t", t.input, -1)
   230  		info := snaptest.MockInfo(c, yml, nil)
   231  		slot := info.Slots["raw-volume"]
   232  
   233  		c.Check(interfaces.BeforePrepareSlot(s.iface, slot), IsNil, Commentf("unexpected error for %q", t.input))
   234  	}
   235  }
   236  
   237  func (s *rawVolumeInterfaceSuite) TestSanitizeSlotUnhappy(c *C) {
   238  	const mockSnapYaml = `name: raw-volume-slot-snap
   239  type: gadget
   240  version: 1.0
   241  slots:
   242    raw-volume:
   243      path: $t
   244  `
   245  
   246  	var testCases = []struct {
   247  		input string
   248  	}{
   249  		{`/dev/hda0`},
   250  		{`/dev/hdt64`},
   251  		{`/dev/hdu1`},
   252  		{`/dev/sda0`},
   253  		{`/dev/sdiv16`},
   254  		{`/dev/sdiw1`},
   255  		{`/dev/i2o/hda0`},
   256  		{`/dev/i20/hddx16`},
   257  		{`/dev/i2o/hddy1`},
   258  		{`/dev/mmcblk0p0`},
   259  		{`/dev/mmcblk999p64`},
   260  		{`/dev/mmcblk1000p1`},
   261  		{`/dev/nvme0p0`},
   262  		{`/dev/nvme99p64`},
   263  		{`/dev/nvme100p1`},
   264  		{`/dev/nvme0n0p1`},
   265  		{`/dev/nvme99n64p1`},
   266  		{`/dev/nvme100n1p1`},
   267  		{`/dev/vda0`},
   268  		{`/dev/vdz64`},
   269  		{`/dev/vdaa1`},
   270  	}
   271  
   272  	for _, t := range testCases {
   273  		yml := strings.Replace(mockSnapYaml, "$t", t.input, -1)
   274  		info := snaptest.MockInfo(c, yml, nil)
   275  		slot := info.Slots["raw-volume"]
   276  
   277  		c.Check(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, `slot "raw-volume-slot-snap:raw-volume" path attribute must be a valid device node`, Commentf("unexpected error for %q", t.input))
   278  	}
   279  }
   280  
   281  func (s *rawVolumeInterfaceSuite) TestSanitizeSlotUnclean(c *C) {
   282  	const mockSnapYaml = `name: raw-volume-slot-snap
   283  type: gadget
   284  version: 1.0
   285  slots:
   286    raw-volume:
   287      path: $t
   288  `
   289  
   290  	var testCases = []struct {
   291  		input string
   292  	}{
   293  		{`/dev/hda1/.`},
   294  		{`/dev/i2o/`},
   295  		{`/dev/./././mmcblk0p1////`},
   296  	}
   297  
   298  	for _, t := range testCases {
   299  		yml := strings.Replace(mockSnapYaml, "$t", t.input, -1)
   300  		info := snaptest.MockInfo(c, yml, nil)
   301  		slot := info.Slots["raw-volume"]
   302  		c.Check(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, `cannot use slot "raw-volume-slot-snap:raw-volume" path ".*": try ".*"`, Commentf("unexpected error for %q", t.input))
   303  	}
   304  }
   305  
   306  func (s *rawVolumeInterfaceSuite) TestUDevSpec(c *C) {
   307  	spec := &udev.Specification{}
   308  	c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPart1, s.testUDev1), IsNil)
   309  	c.Assert(spec.Snippets(), HasLen, 2)
   310  	c.Assert(spec.Snippets()[0], Equals, `# raw-volume
   311  KERNEL=="vda1", TAG+="snap_client-snap_app-accessing-1-part"`)
   312  	c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_client-snap_app-accessing-1-part", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-1-part $devpath $major:$minor"`)
   313  
   314  	spec = &udev.Specification{}
   315  	c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPart2, s.testUDev2), IsNil)
   316  	c.Assert(spec.Snippets(), HasLen, 2)
   317  	c.Assert(spec.Snippets()[0], Equals, `# raw-volume
   318  KERNEL=="mmcblk0p1", TAG+="snap_client-snap_app-accessing-2-part"`)
   319  
   320  	spec = &udev.Specification{}
   321  	c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPart3, s.testUDev3), IsNil)
   322  	c.Assert(spec.Snippets(), HasLen, 2)
   323  	c.Assert(spec.Snippets()[0], Equals, `# raw-volume
   324  KERNEL=="i2o/hda1", TAG+="snap_client-snap_app-accessing-3-part"`)
   325  }
   326  
   327  func (s *rawVolumeInterfaceSuite) TestAppArmorSpec(c *C) {
   328  	spec := &apparmor.Specification{}
   329  	c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPart1, s.testUDev1), IsNil)
   330  	c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-1-part"})
   331  	c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-1-part"), testutil.Contains, `/dev/vda1 rw,`)
   332  	c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-1-part"), testutil.Contains, `capability sys_admin,`)
   333  
   334  	spec = &apparmor.Specification{}
   335  	c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPart2, s.testUDev2), IsNil)
   336  	c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-2-part"})
   337  	c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-2-part"), testutil.Contains, `/dev/mmcblk0p1 rw,`)
   338  	c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-2-part"), testutil.Contains, `capability sys_admin,`)
   339  
   340  	spec = &apparmor.Specification{}
   341  	c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPart3, s.testUDev3), IsNil)
   342  	c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-3-part"})
   343  	c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-3-part"), testutil.Contains, `/dev/i2o/hda1 rw,`)
   344  	c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-3-part"), testutil.Contains, `capability sys_admin,`)
   345  }
   346  
   347  func (s *rawVolumeInterfaceSuite) TestStaticInfo(c *C) {
   348  	si := interfaces.StaticInfoOf(s.iface)
   349  	c.Assert(si.ImplicitOnCore, Equals, false)
   350  	c.Assert(si.ImplicitOnClassic, Equals, false)
   351  	c.Assert(si.Summary, Equals, `allows read/write access to specific disk partition`)
   352  	c.Assert(si.BaseDeclarationSlots, testutil.Contains, "raw-volume")
   353  }
   354  
   355  func (s *rawVolumeInterfaceSuite) TestAutoConnect(c *C) {
   356  	c.Check(s.iface.AutoConnect(nil, nil), Equals, true)
   357  }
   358  
   359  func (s *rawVolumeInterfaceSuite) TestInterfaces(c *C) {
   360  	c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
   361  }