github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/snapstate/handlers_mount_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 snapstate_test
    21  
    22  import (
    23  	"path/filepath"
    24  	"time"
    25  
    26  	. "gopkg.in/check.v1"
    27  
    28  	"github.com/snapcore/snapd/dirs"
    29  	"github.com/snapcore/snapd/osutil"
    30  	"github.com/snapcore/snapd/overlord/snapstate"
    31  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    32  	"github.com/snapcore/snapd/snap"
    33  	"github.com/snapcore/snapd/snap/snaptest"
    34  )
    35  
    36  type mountSnapSuite struct {
    37  	baseHandlerSuite
    38  }
    39  
    40  var _ = Suite(&mountSnapSuite{})
    41  
    42  func (s *mountSnapSuite) SetUpTest(c *C) {
    43  	s.setup(c, nil)
    44  	s.AddCleanup(snapstatetest.MockDeviceModel(DefaultModel()))
    45  }
    46  
    47  func (s *mountSnapSuite) TestDoMountSnapDoesNotRemovesSnaps(c *C) {
    48  	v1 := "name: mock\nversion: 1.0\n"
    49  	testSnap := snaptest.MakeTestSnapWithFiles(c, v1, nil)
    50  
    51  	s.state.Lock()
    52  
    53  	t := s.state.NewTask("mount-snap", "test")
    54  	t.Set("snap-setup", &snapstate.SnapSetup{
    55  		SideInfo: &snap.SideInfo{
    56  			RealName: "foo",
    57  			Revision: snap.R(33),
    58  		},
    59  		SnapPath:     testSnap,
    60  		DownloadInfo: &snap.DownloadInfo{DownloadURL: "https://some"},
    61  	})
    62  	s.state.NewChange("dummy", "...").AddTask(t)
    63  
    64  	s.state.Unlock()
    65  
    66  	s.se.Ensure()
    67  	s.se.Wait()
    68  
    69  	c.Assert(osutil.FileExists(testSnap), Equals, true)
    70  }
    71  
    72  func (s *mountSnapSuite) TestDoUndoMountSnap(c *C) {
    73  	v1 := "name: core\nversion: 1.0\nepoch: 1\n"
    74  	testSnap := snaptest.MakeTestSnapWithFiles(c, v1, nil)
    75  
    76  	s.state.Lock()
    77  	defer s.state.Unlock()
    78  	si1 := &snap.SideInfo{
    79  		RealName: "core",
    80  		Revision: snap.R(1),
    81  	}
    82  	si2 := &snap.SideInfo{
    83  		RealName: "core",
    84  		Revision: snap.R(2),
    85  	}
    86  	snapstate.Set(s.state, "core", &snapstate.SnapState{
    87  		Sequence: []*snap.SideInfo{si1},
    88  		Current:  si1.Revision,
    89  		SnapType: "os",
    90  	})
    91  
    92  	t := s.state.NewTask("mount-snap", "test")
    93  	t.Set("snap-setup", &snapstate.SnapSetup{
    94  		SideInfo: si2,
    95  		SnapPath: testSnap,
    96  	})
    97  	chg := s.state.NewChange("dummy", "...")
    98  	chg.AddTask(t)
    99  
   100  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   101  	terr.WaitFor(t)
   102  	chg.AddTask(terr)
   103  
   104  	s.state.Unlock()
   105  
   106  	for i := 0; i < 3; i++ {
   107  		s.se.Ensure()
   108  		s.se.Wait()
   109  	}
   110  
   111  	s.state.Lock()
   112  
   113  	// ensure undo was called the right way
   114  	c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
   115  		{
   116  			op:  "current",
   117  			old: filepath.Join(dirs.SnapMountDir, "core/1"),
   118  		},
   119  		{
   120  			op:    "setup-snap",
   121  			name:  "core",
   122  			path:  testSnap,
   123  			revno: snap.R(2),
   124  		},
   125  		{
   126  			op:    "undo-setup-snap",
   127  			name:  "core",
   128  			path:  filepath.Join(dirs.SnapMountDir, "core/2"),
   129  			stype: "os",
   130  		},
   131  		{
   132  			op:   "remove-snap-dir",
   133  			name: "core",
   134  			path: filepath.Join(dirs.SnapMountDir, "core"),
   135  		},
   136  	})
   137  
   138  }
   139  
   140  func (s *mountSnapSuite) TestDoMountSnapErrorReadInfo(c *C) {
   141  	v1 := "name: borken\nversion: 1.0\nepoch: 1\n"
   142  	testSnap := snaptest.MakeTestSnapWithFiles(c, v1, nil)
   143  
   144  	s.state.Lock()
   145  	defer s.state.Unlock()
   146  	si1 := &snap.SideInfo{
   147  		RealName: "borken",
   148  		Revision: snap.R(1),
   149  	}
   150  	si2 := &snap.SideInfo{
   151  		RealName: "borken",
   152  		Revision: snap.R(2),
   153  	}
   154  	snapstate.Set(s.state, "borken", &snapstate.SnapState{
   155  		Sequence: []*snap.SideInfo{si1},
   156  		Current:  si1.Revision,
   157  		SnapType: "app",
   158  	})
   159  
   160  	t := s.state.NewTask("mount-snap", "test")
   161  	t.Set("snap-setup", &snapstate.SnapSetup{
   162  		SideInfo: si2,
   163  		SnapPath: testSnap,
   164  	})
   165  	chg := s.state.NewChange("dummy", "...")
   166  	chg.AddTask(t)
   167  
   168  	s.state.Unlock()
   169  
   170  	for i := 0; i < 3; i++ {
   171  		s.se.Ensure()
   172  		s.se.Wait()
   173  	}
   174  
   175  	s.state.Lock()
   176  
   177  	c.Check(chg.Err(), ErrorMatches, `(?s).*cannot read info for "borken" snap.*`)
   178  
   179  	c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
   180  		{
   181  			op:  "current",
   182  			old: filepath.Join(dirs.SnapMountDir, "borken/1"),
   183  		},
   184  		{
   185  			op:    "setup-snap",
   186  			name:  "borken",
   187  			path:  testSnap,
   188  			revno: snap.R(2),
   189  		},
   190  		{
   191  			op:    "undo-setup-snap",
   192  			name:  "borken",
   193  			path:  filepath.Join(dirs.SnapMountDir, "borken/2"),
   194  			stype: "app",
   195  		},
   196  		{
   197  			op:   "remove-snap-dir",
   198  			name: "borken",
   199  			path: filepath.Join(dirs.SnapMountDir, "borken"),
   200  		},
   201  	})
   202  }
   203  
   204  func (s *mountSnapSuite) TestDoMountSnapEpochError(c *C) {
   205  	v1 := "name: some-snap\nversion: 1.0\nepoch: 13\n"
   206  	testSnap := snaptest.MakeTestSnapWithFiles(c, v1, nil)
   207  
   208  	s.state.Lock()
   209  	defer s.state.Unlock()
   210  	si1 := &snap.SideInfo{
   211  		RealName: "some-snap",
   212  		Revision: snap.R(1),
   213  	}
   214  	si2 := &snap.SideInfo{
   215  		RealName: "some-snap",
   216  		Revision: snap.R(2),
   217  	}
   218  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   219  		Sequence: []*snap.SideInfo{si1},
   220  		Current:  si1.Revision,
   221  		SnapType: "app",
   222  	})
   223  
   224  	t := s.state.NewTask("mount-snap", "test")
   225  	t.Set("snap-setup", &snapstate.SnapSetup{
   226  		SideInfo: si2,
   227  		SnapPath: testSnap,
   228  	})
   229  	chg := s.state.NewChange("dummy", "...")
   230  	chg.AddTask(t)
   231  
   232  	s.state.Unlock()
   233  
   234  	for i := 0; i < 3; i++ {
   235  		s.se.Ensure()
   236  		s.se.Wait()
   237  	}
   238  
   239  	s.state.Lock()
   240  
   241  	c.Check(chg.Err(), ErrorMatches, `(?s).* new revision 2 with epoch .* can't read the current epoch of [^ ]*`)
   242  	c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
   243  		{
   244  			op:  "current",
   245  			old: filepath.Join(dirs.SnapMountDir, "some-snap/1"),
   246  		},
   247  	})
   248  }
   249  
   250  func (s *mountSnapSuite) TestDoMountSnapErrorSetupSnap(c *C) {
   251  	v1 := "name: borken\nversion: 1.0\n"
   252  	testSnap := snaptest.MakeTestSnapWithFiles(c, v1, nil)
   253  
   254  	s.state.Lock()
   255  	defer s.state.Unlock()
   256  	si := &snap.SideInfo{
   257  		RealName: "borken-in-setup",
   258  		Revision: snap.R(2),
   259  	}
   260  
   261  	t := s.state.NewTask("mount-snap", "test")
   262  	t.Set("snap-setup", &snapstate.SnapSetup{
   263  		SideInfo: si,
   264  		SnapPath: testSnap,
   265  	})
   266  	chg := s.state.NewChange("dummy", "...")
   267  	chg.AddTask(t)
   268  
   269  	s.state.Unlock()
   270  
   271  	for i := 0; i < 3; i++ {
   272  		s.se.Ensure()
   273  		s.se.Wait()
   274  	}
   275  
   276  	s.state.Lock()
   277  
   278  	c.Check(chg.Err(), ErrorMatches, `(?s).*cannot install snap "borken-in-setup".*`)
   279  
   280  	c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
   281  		{
   282  			op:  "current",
   283  			old: "<no-current>",
   284  		},
   285  		{
   286  			op:    "setup-snap",
   287  			name:  "borken-in-setup",
   288  			path:  testSnap,
   289  			revno: snap.R(2),
   290  		},
   291  		{
   292  			op:   "remove-snap-dir",
   293  			name: "borken-in-setup",
   294  			path: filepath.Join(dirs.SnapMountDir, "borken-in-setup"),
   295  		},
   296  	})
   297  }
   298  
   299  func (s *mountSnapSuite) TestDoMountSnapUndoError(c *C) {
   300  	v1 := "name: borken-undo-setup\nversion: 1.0\nepoch: 1\n"
   301  	testSnap := snaptest.MakeTestSnapWithFiles(c, v1, nil)
   302  
   303  	s.state.Lock()
   304  	defer s.state.Unlock()
   305  	si1 := &snap.SideInfo{
   306  		RealName: "borken-undo-setup",
   307  		Revision: snap.R(1),
   308  	}
   309  	si2 := &snap.SideInfo{
   310  		RealName: "borken-undo-setup",
   311  		Revision: snap.R(2),
   312  	}
   313  	snapstate.Set(s.state, "borken-undo-setup", &snapstate.SnapState{
   314  		Sequence: []*snap.SideInfo{si1},
   315  		Current:  si1.Revision,
   316  		SnapType: "app",
   317  	})
   318  
   319  	t := s.state.NewTask("mount-snap", "test")
   320  	t.Set("snap-setup", &snapstate.SnapSetup{
   321  		SideInfo: si2,
   322  		SnapPath: testSnap,
   323  	})
   324  	chg := s.state.NewChange("dummy", "...")
   325  	chg.AddTask(t)
   326  
   327  	s.state.Unlock()
   328  
   329  	for i := 0; i < 3; i++ {
   330  		s.se.Ensure()
   331  		s.se.Wait()
   332  	}
   333  
   334  	s.state.Lock()
   335  
   336  	c.Check(chg.Err(), ErrorMatches, `(?s).*cannot undo partial setup snap "borken-undo-setup": cannot undo setup of "borken-undo-setup" snap.*cannot read info for "borken-undo-setup" snap.*`)
   337  
   338  	c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
   339  		{
   340  			op:  "current",
   341  			old: filepath.Join(dirs.SnapMountDir, "borken-undo-setup/1"),
   342  		},
   343  		{
   344  			op:    "setup-snap",
   345  			name:  "borken-undo-setup",
   346  			path:  testSnap,
   347  			revno: snap.R(2),
   348  		},
   349  		{
   350  			op:    "undo-setup-snap",
   351  			name:  "borken-undo-setup",
   352  			path:  filepath.Join(dirs.SnapMountDir, "borken-undo-setup/2"),
   353  			stype: "app",
   354  		},
   355  		{
   356  			op:   "remove-snap-dir",
   357  			name: "borken-undo-setup",
   358  			path: filepath.Join(dirs.SnapMountDir, "borken-undo-setup"),
   359  		},
   360  	})
   361  }
   362  
   363  func (s *mountSnapSuite) TestDoMountSnapErrorNotFound(c *C) {
   364  	r := snapstate.MockMountPollInterval(10 * time.Millisecond)
   365  	defer r()
   366  
   367  	v1 := "name: not-there\nversion: 1.0\nepoch: 1\n"
   368  	testSnap := snaptest.MakeTestSnapWithFiles(c, v1, nil)
   369  
   370  	s.state.Lock()
   371  	defer s.state.Unlock()
   372  	si1 := &snap.SideInfo{
   373  		RealName: "not-there",
   374  		Revision: snap.R(1),
   375  	}
   376  	si2 := &snap.SideInfo{
   377  		RealName: "not-there",
   378  		Revision: snap.R(2),
   379  	}
   380  	snapstate.Set(s.state, "not-there", &snapstate.SnapState{
   381  		Sequence: []*snap.SideInfo{si1},
   382  		Current:  si1.Revision,
   383  		SnapType: "app",
   384  	})
   385  
   386  	t := s.state.NewTask("mount-snap", "test")
   387  	t.Set("snap-setup", &snapstate.SnapSetup{
   388  		SideInfo: si2,
   389  		SnapPath: testSnap,
   390  	})
   391  	chg := s.state.NewChange("dummy", "...")
   392  	chg.AddTask(t)
   393  
   394  	s.state.Unlock()
   395  
   396  	for i := 0; i < 3; i++ {
   397  		s.se.Ensure()
   398  		s.se.Wait()
   399  	}
   400  
   401  	s.state.Lock()
   402  
   403  	c.Check(chg.Err(), ErrorMatches, `(?s).*cannot proceed, expected snap "not-there" revision 2 to be mounted but is not.*`)
   404  
   405  	c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
   406  		{
   407  			op:  "current",
   408  			old: filepath.Join(dirs.SnapMountDir, "not-there/1"),
   409  		},
   410  		{
   411  			op:    "setup-snap",
   412  			name:  "not-there",
   413  			path:  testSnap,
   414  			revno: snap.R(2),
   415  		},
   416  		{
   417  			op:    "undo-setup-snap",
   418  			name:  "not-there",
   419  			path:  filepath.Join(dirs.SnapMountDir, "not-there/2"),
   420  			stype: "app",
   421  		},
   422  		{
   423  			op:   "remove-snap-dir",
   424  			name: "not-there",
   425  			path: filepath.Join(dirs.SnapMountDir, "not-there"),
   426  		},
   427  	})
   428  }
   429  
   430  func (s *mountSnapSuite) TestDoMountNotMountedRetryRetry(c *C) {
   431  	r := snapstate.MockMountPollInterval(10 * time.Millisecond)
   432  	defer r()
   433  	n := 0
   434  	slowMountedReadInfo := func(name string, si *snap.SideInfo) (*snap.Info, error) {
   435  		n++
   436  		if n < 3 {
   437  			return nil, &snap.NotFoundError{Snap: "not-there", Revision: si.Revision}
   438  		}
   439  		return &snap.Info{
   440  			SideInfo: *si,
   441  		}, nil
   442  	}
   443  
   444  	r1 := snapstate.MockSnapReadInfo(slowMountedReadInfo)
   445  	defer r1()
   446  
   447  	v1 := "name: not-there\nversion: 1.0\n"
   448  	testSnap := snaptest.MakeTestSnapWithFiles(c, v1, nil)
   449  
   450  	s.state.Lock()
   451  	defer s.state.Unlock()
   452  	si1 := &snap.SideInfo{
   453  		RealName: "not-there",
   454  		Revision: snap.R(1),
   455  	}
   456  	si2 := &snap.SideInfo{
   457  		RealName: "not-there",
   458  		Revision: snap.R(2),
   459  	}
   460  	snapstate.Set(s.state, "not-there", &snapstate.SnapState{
   461  		Sequence: []*snap.SideInfo{si1},
   462  		Current:  si1.Revision,
   463  		SnapType: "app",
   464  	})
   465  
   466  	t := s.state.NewTask("mount-snap", "test")
   467  	t.Set("snap-setup", &snapstate.SnapSetup{
   468  		SideInfo: si2,
   469  		SnapPath: testSnap,
   470  	})
   471  	chg := s.state.NewChange("dummy", "...")
   472  	chg.AddTask(t)
   473  
   474  	s.state.Unlock()
   475  
   476  	for i := 0; i < 3; i++ {
   477  		s.se.Ensure()
   478  		s.se.Wait()
   479  	}
   480  
   481  	s.state.Lock()
   482  
   483  	c.Check(chg.IsReady(), Equals, true)
   484  	c.Check(chg.Err(), IsNil)
   485  
   486  	c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
   487  		{
   488  			op:  "current",
   489  			old: filepath.Join(dirs.SnapMountDir, "not-there/1"),
   490  		},
   491  		{
   492  			op:    "setup-snap",
   493  			name:  "not-there",
   494  			path:  testSnap,
   495  			revno: snap.R(2),
   496  		},
   497  	})
   498  }