github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/snapstate/policy/canremove_test.go (about)

     1  package policy_test
     2  
     3  import (
     4  	"errors"
     5  
     6  	"gopkg.in/check.v1"
     7  
     8  	"github.com/snapcore/snapd/boot/boottest"
     9  	"github.com/snapcore/snapd/bootloader"
    10  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    11  	"github.com/snapcore/snapd/dirs"
    12  	"github.com/snapcore/snapd/overlord/snapstate"
    13  	"github.com/snapcore/snapd/overlord/snapstate/policy"
    14  	"github.com/snapcore/snapd/overlord/state"
    15  	"github.com/snapcore/snapd/snap"
    16  	"github.com/snapcore/snapd/snap/snaptest"
    17  	"github.com/snapcore/snapd/testutil"
    18  )
    19  
    20  type canRemoveSuite struct {
    21  	testutil.BaseTest
    22  	st *state.State
    23  
    24  	bootloader *boottest.Bootenv16
    25  }
    26  
    27  var _ = check.Suite(&canRemoveSuite{})
    28  
    29  func (s *canRemoveSuite) SetUpTest(c *check.C) {
    30  	s.BaseTest.SetUpTest(c)
    31  	dirs.SetRootDir(c.MkDir())
    32  	s.st = state.New(nil)
    33  
    34  	s.bootloader = boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir()))
    35  	bootloader.Force(s.bootloader)
    36  	s.bootloader.SetBootBase("base_99.snap")
    37  	s.bootloader.SetBootKernel("kernel_99.snap")
    38  	s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    39  }
    40  
    41  func (s *canRemoveSuite) TearDownTest(c *check.C) {
    42  	dirs.SetRootDir("/")
    43  	bootloader.Force(nil)
    44  	s.BaseTest.TearDownTest(c)
    45  }
    46  
    47  var (
    48  	coreDev      = boottest.MockDevice("some-snap")
    49  	ephemeralDev = boottest.MockDevice("some-snap@install")
    50  	classicDev   = boottest.MockDevice("")
    51  )
    52  
    53  func (s *canRemoveSuite) TestAppAreOK(c *check.C) {
    54  	snapst := &snapstate.SnapState{}
    55  	c.Check(policy.NewAppPolicy().CanRemove(s.st, snapst, snap.R(0), coreDev), check.IsNil)
    56  	c.Check(policy.NewAppPolicy().CanRemove(s.st, snapst, snap.R(1), coreDev), check.IsNil)
    57  }
    58  
    59  func (s *canRemoveSuite) TestRequiredAppIsNotOK(c *check.C) {
    60  	snapst := &snapstate.SnapState{Flags: snapstate.Flags{Required: true}}
    61  	c.Check(policy.NewAppPolicy().CanRemove(s.st, snapst, snap.R(0), coreDev), check.Equals, policy.ErrRequired)
    62  	c.Check(policy.NewAppPolicy().CanRemove(s.st, snapst, snap.R(1), coreDev), check.IsNil)
    63  }
    64  
    65  func (s *canRemoveSuite) TestEphemeralAppIsNotOK(c *check.C) {
    66  	snapst := &snapstate.SnapState{}
    67  	c.Check(policy.NewAppPolicy().CanRemove(s.st, snapst, snap.R(0), ephemeralDev), check.DeepEquals, policy.ErrEphemeralSnapsNotRemovalable)
    68  }
    69  
    70  func (s *canRemoveSuite) TestOneGadgetRevisionIsOK(c *check.C) {
    71  	snapst := &snapstate.SnapState{
    72  		Current:  snap.R(1),
    73  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "gadget"}},
    74  	}
    75  	c.Check(policy.NewGadgetPolicy("gadget").CanRemove(s.st, snapst, snap.R(1), coreDev), check.IsNil)
    76  }
    77  
    78  func (s *canRemoveSuite) TestOtherGadgetIsOK(c *check.C) {
    79  	snapst := &snapstate.SnapState{
    80  		Current:  snap.R(1),
    81  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "gadget"}},
    82  	}
    83  	c.Check(policy.NewGadgetPolicy("gadget2").CanRemove(s.st, snapst, snap.R(0), coreDev), check.IsNil)
    84  }
    85  
    86  func (s *canRemoveSuite) TestEphemeralGadgetIsNotOK(c *check.C) {
    87  	snapst := &snapstate.SnapState{
    88  		Current:  snap.R(1),
    89  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "gadget"}},
    90  	}
    91  	c.Check(policy.NewGadgetPolicy("gadget2").CanRemove(s.st, snapst, snap.R(0), ephemeralDev), check.DeepEquals, policy.ErrEphemeralSnapsNotRemovalable)
    92  }
    93  
    94  func (s *canRemoveSuite) TestLastGadgetsAreNotOK(c *check.C) {
    95  	snapst := &snapstate.SnapState{
    96  		Current:  snap.R(1),
    97  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "gadget"}},
    98  	}
    99  	c.Check(policy.NewGadgetPolicy("gadget").CanRemove(s.st, snapst, snap.R(0), coreDev), check.Equals, policy.ErrIsModel)
   100  }
   101  
   102  func (s *canRemoveSuite) TestLastOSAndKernelAreNotOK(c *check.C) {
   103  	s.st.Lock()
   104  	defer s.st.Unlock()
   105  
   106  	snapst := &snapstate.SnapState{
   107  		Current:  snap.R(1),
   108  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "kernel"}},
   109  	}
   110  	// model base is "" -> OS can't be removed
   111  	c.Check(policy.NewOSPolicy("").CanRemove(s.st, snapst, snap.R(0), coreDev), check.Equals, policy.ErrIsModel)
   112  	// (well, single revisions are ok)
   113  	c.Check(policy.NewOSPolicy("").CanRemove(s.st, snapst, snap.R(1), coreDev), check.IsNil)
   114  	// model kernel == snap kernel -> can't be removed
   115  	c.Check(policy.NewKernelPolicy("kernel").CanRemove(s.st, snapst, snap.R(0), coreDev), check.Equals, policy.ErrIsModel)
   116  	// (well, single revisions are ok)
   117  	c.Check(policy.NewKernelPolicy("kernel").CanRemove(s.st, snapst, snap.R(1), coreDev), check.IsNil)
   118  }
   119  
   120  func (s *canRemoveSuite) TestOSInUseNotOK(c *check.C) {
   121  	s.st.Lock()
   122  	defer s.st.Unlock()
   123  
   124  	snapst := &snapstate.SnapState{
   125  		Current:  snap.R(1),
   126  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "core"}},
   127  	}
   128  	// normally this would be fine
   129  	c.Check(policy.NewOSPolicy("").CanRemove(s.st, snapst, snap.R(1), coreDev), check.IsNil)
   130  	// but not if it's the one we booted
   131  	s.bootloader.SetBootBase("core_1.snap")
   132  	c.Check(policy.NewOSPolicy("").CanRemove(s.st, snapst, snap.R(1), coreDev), check.Equals, policy.ErrInUseForBoot)
   133  }
   134  
   135  func (s *canRemoveSuite) TestOSRequiredNotOK(c *check.C) {
   136  	s.st.Lock()
   137  	defer s.st.Unlock()
   138  
   139  	snapst := &snapstate.SnapState{
   140  		Current:  snap.R(1),
   141  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "core"}},
   142  		Flags:    snapstate.Flags{Required: true},
   143  	}
   144  	// can't remove them all if they're required
   145  	c.Check(policy.NewOSPolicy("core18").CanRemove(s.st, snapst, snap.R(0), coreDev), check.Equals, policy.ErrRequired)
   146  	// but a single rev is ok
   147  	c.Check(policy.NewOSPolicy("core18").CanRemove(s.st, snapst, snap.R(1), coreDev), check.IsNil)
   148  }
   149  
   150  func (s *canRemoveSuite) TestOSUbuntuCoreOK(c *check.C) {
   151  	s.st.Lock()
   152  	defer s.st.Unlock()
   153  
   154  	snapst := &snapstate.SnapState{
   155  		Current:  snap.R(1),
   156  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "ubuntu-core"}},
   157  	}
   158  	c.Check(policy.NewOSPolicy("").CanRemove(s.st, snapst, snap.R(0), coreDev), check.IsNil)
   159  }
   160  
   161  func (s *canRemoveSuite) TestKernelBootInUseIsKept(c *check.C) {
   162  	s.st.Lock()
   163  	defer s.st.Unlock()
   164  
   165  	snapst := &snapstate.SnapState{
   166  		Current:  snap.R(1),
   167  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "kernel"}},
   168  	}
   169  
   170  	s.bootloader.SetBootKernel("kernel_1.snap")
   171  
   172  	c.Check(policy.NewKernelPolicy("kernel").CanRemove(s.st, snapst, snap.R(1), coreDev), check.Equals, policy.ErrInUseForBoot)
   173  }
   174  
   175  func (s *canRemoveSuite) TestBootInUseError(c *check.C) {
   176  	s.st.Lock()
   177  	defer s.st.Unlock()
   178  
   179  	snapst := &snapstate.SnapState{
   180  		Current:  snap.R(1),
   181  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "kernel"}},
   182  	}
   183  
   184  	bootloader.ForceError(errors.New("broken bootloader"))
   185  
   186  	c.Check(policy.NewKernelPolicy("kernel").CanRemove(s.st, snapst, snap.R(1), coreDev), check.ErrorMatches, `cannot get boot settings: broken bootloader`)
   187  }
   188  
   189  func (s *canRemoveSuite) TestBaseInUseIsKept(c *check.C) {
   190  	s.st.Lock()
   191  	defer s.st.Unlock()
   192  
   193  	snapst := &snapstate.SnapState{
   194  		Current:  snap.R(1),
   195  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "core18"}},
   196  	}
   197  	// if not used for boot, removing a single one is ok
   198  	c.Check(policy.NewBasePolicy("core18").CanRemove(s.st, snapst, snap.R(1), coreDev), check.IsNil)
   199  	// but not all
   200  	c.Check(policy.NewBasePolicy("core18").CanRemove(s.st, snapst, snap.R(0), coreDev), check.Equals, policy.ErrIsModel)
   201  
   202  	// if in use for boot, not even one
   203  	s.bootloader.SetBootBase("core18_1.snap")
   204  	c.Check(policy.NewBasePolicy("core18").CanRemove(s.st, snapst, snap.R(1), coreDev), check.Equals, policy.ErrInUseForBoot)
   205  }
   206  
   207  func (s *canRemoveSuite) TestRemoveNonModelKernelIsOk(c *check.C) {
   208  	snapst := &snapstate.SnapState{
   209  		Current:  snap.R(1),
   210  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "other-non-model-kernel"}},
   211  	}
   212  
   213  	c.Check(policy.NewKernelPolicy("kernel").CanRemove(s.st, snapst, snap.R(0), coreDev), check.IsNil)
   214  }
   215  
   216  func (s *canRemoveSuite) TestRemoveEphemeralKernelIsNotOK(c *check.C) {
   217  	snapst := &snapstate.SnapState{
   218  		Current:  snap.R(1),
   219  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "other-non-model-kernel"}},
   220  	}
   221  
   222  	c.Check(policy.NewKernelPolicy("kernel").CanRemove(s.st, snapst, snap.R(0), ephemeralDev), check.DeepEquals, policy.ErrEphemeralSnapsNotRemovalable)
   223  }
   224  
   225  func (s *canRemoveSuite) TestLastOSWithModelBaseIsOk(c *check.C) {
   226  	s.st.Lock()
   227  	defer s.st.Unlock()
   228  
   229  	snapst := &snapstate.SnapState{
   230  		Current:  snap.R(1),
   231  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "core"}},
   232  	}
   233  
   234  	c.Check(policy.NewOSPolicy("core18").CanRemove(s.st, snapst, snap.R(0), coreDev), check.IsNil)
   235  }
   236  
   237  func (s *canRemoveSuite) TestEphemeralCoreIsNotOK(c *check.C) {
   238  	s.st.Lock()
   239  	defer s.st.Unlock()
   240  
   241  	snapst := &snapstate.SnapState{
   242  		Current:  snap.R(1),
   243  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "core"}},
   244  	}
   245  
   246  	c.Check(policy.NewOSPolicy("core20").CanRemove(s.st, snapst, snap.R(0), ephemeralDev), check.DeepEquals, policy.ErrEphemeralSnapsNotRemovalable)
   247  }
   248  
   249  func (s *canRemoveSuite) TestLastOSWithModelBaseButOsInUse(c *check.C) {
   250  	s.st.Lock()
   251  	defer s.st.Unlock()
   252  
   253  	// pretend we have a snap installed that has no base (which means
   254  	// it needs core)
   255  	si := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}
   256  	snaptest.MockSnap(c, "name: some-snap\nversion: 1.0", si)
   257  	snapstate.Set(s.st, "some-snap", &snapstate.SnapState{
   258  		Sequence: []*snap.SideInfo{si},
   259  		Current:  snap.R(1),
   260  	})
   261  
   262  	// now pretend we want to remove the core snap
   263  	snapst := &snapstate.SnapState{
   264  		Current:  snap.R(1),
   265  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "core"}},
   266  	}
   267  	c.Check(policy.NewOSPolicy("core18").CanRemove(s.st, snapst, snap.R(0), coreDev), check.DeepEquals, policy.InUseByErr("some-snap"))
   268  }
   269  
   270  func (s *canRemoveSuite) TestLastOSWithModelBaseButOsInUseByGadget(c *check.C) {
   271  	s.st.Lock()
   272  	defer s.st.Unlock()
   273  
   274  	// pretend we have a gadget snap installed that has no base (which means
   275  	// it needs core)
   276  	si := &snap.SideInfo{RealName: "some-gadget", SnapID: "some-gadget-id", Revision: snap.R(1)}
   277  	snaptest.MockSnap(c, "name: some-gadget\ntype: gadget\nversion: 1.0", si)
   278  	snapstate.Set(s.st, "some-gadget", &snapstate.SnapState{
   279  		Sequence: []*snap.SideInfo{si},
   280  		Current:  snap.R(1),
   281  		SnapType: "gadget",
   282  	})
   283  
   284  	// now pretend we want to remove the core snap
   285  	snapst := &snapstate.SnapState{
   286  		Current:  snap.R(1),
   287  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "core"}},
   288  	}
   289  	c.Check(policy.NewOSPolicy("core18").CanRemove(s.st, snapst, snap.R(0), coreDev), check.DeepEquals, policy.InUseByErr("some-gadget"))
   290  }
   291  
   292  func (s *canRemoveSuite) TestBaseUnused(c *check.C) {
   293  	s.st.Lock()
   294  	defer s.st.Unlock()
   295  
   296  	snapst := &snapstate.SnapState{
   297  		Current:  snap.R(1),
   298  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "foo"}},
   299  	}
   300  
   301  	c.Check(policy.NewBasePolicy("core18").CanRemove(s.st, snapst, snap.R(1), coreDev), check.IsNil)
   302  	c.Check(policy.NewBasePolicy("core18").CanRemove(s.st, snapst, snap.R(0), coreDev), check.IsNil)
   303  }
   304  
   305  func (s *canRemoveSuite) TestEphemeralBaseIsNotOK(c *check.C) {
   306  	s.st.Lock()
   307  	defer s.st.Unlock()
   308  
   309  	snapst := &snapstate.SnapState{
   310  		Current:  snap.R(1),
   311  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "foo"}},
   312  	}
   313  
   314  	c.Check(policy.NewBasePolicy("core18").CanRemove(s.st, snapst, snap.R(1), ephemeralDev), check.DeepEquals, policy.ErrEphemeralSnapsNotRemovalable)
   315  }
   316  
   317  func (s *canRemoveSuite) TestBaseUnusedButRequired(c *check.C) {
   318  	s.st.Lock()
   319  	defer s.st.Unlock()
   320  
   321  	snapst := &snapstate.SnapState{
   322  		Current:  snap.R(1),
   323  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "foo"}},
   324  		Flags:    snapstate.Flags{Required: true},
   325  	}
   326  
   327  	c.Check(policy.NewBasePolicy("core18").CanRemove(s.st, snapst, snap.R(1), coreDev), check.IsNil)
   328  	c.Check(policy.NewBasePolicy("core18").CanRemove(s.st, snapst, snap.R(0), coreDev), check.Equals, policy.ErrRequired)
   329  }
   330  
   331  func (s *canRemoveSuite) TestBaseInUse(c *check.C) {
   332  	s.st.Lock()
   333  	defer s.st.Unlock()
   334  
   335  	// pretend we have a snap installed that uses "some-base"
   336  	si := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}
   337  	snaptest.MockSnap(c, "name: some-snap\nversion: 1.0\nbase: some-base", si)
   338  	snapstate.Set(s.st, "some-snap", &snapstate.SnapState{
   339  		Sequence: []*snap.SideInfo{si},
   340  		Current:  snap.R(1),
   341  	})
   342  
   343  	// pretend now we want to remove "some-base"
   344  	snapst := &snapstate.SnapState{
   345  		Current:  snap.R(1),
   346  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "some-base"}},
   347  	}
   348  	c.Check(policy.NewBasePolicy("core18").CanRemove(s.st, snapst, snap.R(0), coreDev), check.DeepEquals, policy.InUseByErr("some-snap"))
   349  }
   350  
   351  func (s *canRemoveSuite) TestBaseInUseBrokenApp(c *check.C) {
   352  	s.st.Lock()
   353  	defer s.st.Unlock()
   354  
   355  	// pretend we have a snap installed that uses "some-base"
   356  	si := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}
   357  	si2 := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}
   358  	snaptest.MockSnap(c, "name: some-snap\nversion: 1.0\nbase: some-base", si)
   359  	// NOTE no snaptest.MockSnap for si2 -> snap is actually broken
   360  	snapstate.Set(s.st, "some-snap", &snapstate.SnapState{
   361  		Sequence: []*snap.SideInfo{si2, si},
   362  		Current:  snap.R(2),
   363  	})
   364  
   365  	// pretend now we want to remove "some-base"
   366  	snapst := &snapstate.SnapState{
   367  		Current:  snap.R(1),
   368  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "some-base"}},
   369  	}
   370  	c.Check(policy.NewBasePolicy("core18").CanRemove(s.st, snapst, snap.R(0), coreDev), check.DeepEquals, policy.InUseByErr("some-snap"))
   371  }
   372  
   373  func (s *canRemoveSuite) TestBaseInUseOtherRevision(c *check.C) {
   374  	s.st.Lock()
   375  	defer s.st.Unlock()
   376  
   377  	// pretend we have a snap installed that uses "some-base"
   378  	si := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}
   379  	si2 := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}
   380  	// older revision uses base
   381  	snaptest.MockSnap(c, "name: some-snap\nversion: 1.0\nbase: some-base", si)
   382  	// new one does not
   383  	snaptest.MockSnap(c, "name: some-snap\nversion: 1.0\n", si2)
   384  	snapstate.Set(s.st, "some-snap", &snapstate.SnapState{
   385  		Active:   true,
   386  		Sequence: []*snap.SideInfo{si, si2},
   387  		Current:  snap.R(2),
   388  	})
   389  
   390  	// pretend now we want to remove "some-base"
   391  	snapst := &snapstate.SnapState{
   392  		Current:  snap.R(1),
   393  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "some-base"}},
   394  	}
   395  	// revision 1 requires some-base
   396  	c.Check(policy.NewBasePolicy("core18").CanRemove(s.st, snapst, snap.R(0), coreDev), check.DeepEquals, policy.InUseByErr("some-snap"))
   397  
   398  	// now pretend we want to remove the core snap
   399  	snapst.Sequence[0].RealName = "core"
   400  	// but revision 2 requires core
   401  	c.Check(policy.NewOSPolicy("core18").CanRemove(s.st, snapst, snap.R(0), coreDev), check.DeepEquals, policy.InUseByErr("some-snap"))
   402  }
   403  
   404  func (s *canRemoveSuite) TestSnapdTypePolicy(c *check.C) {
   405  	s.st.Lock()
   406  	defer s.st.Unlock()
   407  
   408  	si := &snap.SideInfo{Revision: snap.R(1), RealName: "snapd"}
   409  	snapst := &snapstate.SnapState{
   410  		Current:  snap.R(1),
   411  		Sequence: []*snap.SideInfo{si},
   412  	}
   413  
   414  	// snapd cannot be removed on core
   415  	onClassic := false
   416  	c.Check(policy.NewSnapdPolicy(onClassic).CanRemove(s.st, snapst, snap.R(0), coreDev), check.Equals, policy.ErrSnapdNotRemovableOnCore)
   417  	// but single revisions can be removed
   418  	c.Check(policy.NewSnapdPolicy(onClassic).CanRemove(s.st, snapst, snap.R(1), coreDev), check.IsNil)
   419  	// but not in ephemeral mode
   420  	c.Check(policy.NewSnapdPolicy(onClassic).CanRemove(s.st, snapst, snap.R(1), ephemeralDev), check.DeepEquals, policy.ErrEphemeralSnapsNotRemovalable)
   421  
   422  	// snapd *can* be removed on classic if its the last snap
   423  	onClassic = true
   424  	snapstate.Set(s.st, "snapd", &snapstate.SnapState{
   425  		Current:  snap.R(1),
   426  		Sequence: []*snap.SideInfo{si},
   427  	})
   428  	c.Check(policy.NewSnapdPolicy(onClassic).CanRemove(s.st, snapst, snap.R(0), classicDev), check.IsNil)
   429  
   430  	// but it cannot be removed when there are more snaps installed
   431  	snapstate.Set(s.st, "other-snap", &snapstate.SnapState{
   432  		Current:  snap.R(1),
   433  		Sequence: []*snap.SideInfo{{Revision: snap.R(1), RealName: "other-snap"}},
   434  	})
   435  	c.Check(policy.NewSnapdPolicy(onClassic).CanRemove(s.st, snapst, snap.R(0), classicDev), check.Equals, policy.ErrSnapdNotYetRemovableOnClassic)
   436  }