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 }