gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snap-update-ns/main_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 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 main_test 21 22 import ( 23 "bytes" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "testing" 29 30 . "gopkg.in/check.v1" 31 32 update "gitee.com/mysnapcore/mysnapd/cmd/snap-update-ns" 33 "gitee.com/mysnapcore/mysnapd/dirs" 34 "gitee.com/mysnapcore/mysnapd/features" 35 "gitee.com/mysnapcore/mysnapd/logger" 36 "gitee.com/mysnapcore/mysnapd/osutil" 37 "gitee.com/mysnapcore/mysnapd/sandbox/cgroup" 38 "gitee.com/mysnapcore/mysnapd/testutil" 39 ) 40 41 func Test(t *testing.T) { TestingT(t) } 42 43 type mainSuite struct { 44 testutil.BaseTest 45 as *update.Assumptions 46 log *bytes.Buffer 47 } 48 49 var _ = Suite(&mainSuite{}) 50 51 func (s *mainSuite) SetUpTest(c *C) { 52 s.BaseTest.SetUpTest(c) 53 s.as = &update.Assumptions{} 54 buf, restore := logger.MockLogger() 55 s.AddCleanup(restore) 56 s.log = buf 57 s.AddCleanup(cgroup.MockVersion(cgroup.V1, nil)) 58 } 59 60 func (s *mainSuite) TestExecuteMountProfileUpdate(c *C) { 61 dirs.SetRootDir(c.MkDir()) 62 defer dirs.SetRootDir("/") 63 64 // mount targets look at the the actual local filesystem 65 if !osutil.IsDirectory("/usr/share/fonts") || !osutil.IsDirectory("/usr/local/share/fonts") { 66 c.Skip("missing local directories (/usr/share/fonts or /usr/local/share/fonts)") 67 } 68 69 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 70 return nil, nil 71 }) 72 defer restore() 73 74 snapName := "foo" 75 desiredProfileContent := `/var/lib/snapd/hostfs/usr/share/fonts /usr/share/fonts none bind,ro 0 0 76 /var/lib/snapd/hostfs/usr/local/share/fonts /usr/local/share/fonts none bind,ro 0 0` 77 78 desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) 79 err := os.MkdirAll(filepath.Dir(desiredProfilePath), 0755) 80 c.Assert(err, IsNil) 81 err = ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644) 82 c.Assert(err, IsNil) 83 84 currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) 85 err = os.MkdirAll(filepath.Dir(currentProfilePath), 0755) 86 c.Assert(err, IsNil) 87 err = ioutil.WriteFile(currentProfilePath, nil, 0644) 88 c.Assert(err, IsNil) 89 90 upCtx := update.NewSystemProfileUpdateContext(snapName, false) 91 err = update.ExecuteMountProfileUpdate(upCtx) 92 c.Assert(err, IsNil) 93 94 c.Check(currentProfilePath, testutil.FileEquals, `/var/lib/snapd/hostfs/usr/local/share/fonts /usr/local/share/fonts none bind,ro 0 0 95 /var/lib/snapd/hostfs/usr/share/fonts /usr/share/fonts none bind,ro 0 0 96 `) 97 } 98 99 func (s *mainSuite) TestAddingSyntheticChanges(c *C) { 100 dirs.SetRootDir(c.MkDir()) 101 defer dirs.SetRootDir("/") 102 103 // The snap `mysnap` wishes to export it's usr/share/mysnap directory and 104 // make it appear as if it was in /usr/share/mysnap directly. 105 const snapName = "mysnap" 106 const currentProfileContent = "" 107 const desiredProfileContent = "/snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro 0 0" 108 109 currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) 110 desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) 111 112 c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil) 113 c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil) 114 c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil) 115 c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil) 116 117 // In order to make that work, /usr/share had to be converted to a writable 118 // mimic. Some actions were performed under the hood and now we see a 119 // subset of them as synthetic changes here. 120 // 121 // Note that if you compare this to the code that plans a writable mimic 122 // you will see that there are additional changes that are _not_ 123 // represented here. The changes have only one goal: tell 124 // snap-update-ns how the mimic can be undone in case it is no longer 125 // needed. 126 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 127 // The change that we were asked to perform is to create a bind mount 128 // from within the snap to /usr/share/mysnap. 129 c.Assert(chg, DeepEquals, &update.Change{ 130 Action: update.Mount, Entry: osutil.MountEntry{ 131 Name: "/snap/mysnap/42/usr/share/mysnap", 132 Dir: "/usr/share/mysnap", Type: "none", 133 Options: []string{"bind", "ro"}}}) 134 synthetic := []*update.Change{ 135 // The original directory (which was a part of the core snap and is 136 // read only) was hidden with a tmpfs. 137 {Action: update.Mount, Entry: osutil.MountEntry{ 138 Dir: "/usr/share", Name: "tmpfs", Type: "tmpfs", 139 Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}}}, 140 // For the sake of brevity we will only represent a few of the 141 // entries typically there. Normally this list can get quite long. 142 // Also note that the entry is a little fake. In reality it was 143 // constructed using a temporary bind mount that contained the 144 // original mount entries of /usr/share but this fact was lost. 145 // Again, the only point of this entry is to correctly perform an 146 // undo operation when /usr/share/mysnap is no longer needed. 147 {Action: update.Mount, Entry: osutil.MountEntry{ 148 Dir: "/usr/share/adduser", Name: "/usr/share/adduser", 149 Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}}}, 150 {Action: update.Mount, Entry: osutil.MountEntry{ 151 Dir: "/usr/share/awk", Name: "/usr/share/awk", 152 Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}}}, 153 } 154 return synthetic, nil 155 }) 156 defer restore() 157 158 upCtx := update.NewSystemProfileUpdateContext(snapName, false) 159 c.Assert(update.ExecuteMountProfileUpdate(upCtx), IsNil) 160 161 c.Check(currentProfilePath, testutil.FileEquals, 162 `tmpfs /usr/share tmpfs x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 163 /usr/share/adduser /usr/share/adduser none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 164 /usr/share/awk /usr/share/awk none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 165 /snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro 0 0 166 `) 167 } 168 169 func (s *mainSuite) TestRemovingSyntheticChanges(c *C) { 170 dirs.SetRootDir(c.MkDir()) 171 defer dirs.SetRootDir("/") 172 173 c.Assert(os.MkdirAll(dirs.FeaturesDir, 0755), IsNil) 174 c.Assert(ioutil.WriteFile(features.RobustMountNamespaceUpdates.ControlFile(), []byte(nil), 0644), IsNil) 175 176 // The snap `mysnap` no longer wishes to export it's usr/share/mysnap 177 // directory. All the synthetic changes that were associated with that mount 178 // entry can be discarded. 179 const snapName = "mysnap" 180 const currentProfileContent = `tmpfs /usr/share tmpfs x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 181 /usr/share/adduser /usr/share/adduser none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 182 /usr/share/awk /usr/share/awk none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 183 /snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro 0 0 184 ` 185 const desiredProfileContent = "" 186 187 currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) 188 desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) 189 190 c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil) 191 c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil) 192 c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil) 193 c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil) 194 195 n := -1 196 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 197 n++ 198 switch n { 199 case 0: 200 c.Assert(chg, DeepEquals, &update.Change{ 201 Action: update.Unmount, 202 Entry: osutil.MountEntry{ 203 Name: "/snap/mysnap/42/usr/share/mysnap", 204 Dir: "/usr/share/mysnap", Type: "none", 205 Options: []string{"bind", "ro", "x-snapd.detach"}, 206 }, 207 }) 208 case 1: 209 c.Check(chg, DeepEquals, &update.Change{ 210 Action: update.Unmount, 211 Entry: osutil.MountEntry{ 212 Name: "/usr/share/awk", Dir: "/usr/share/awk", Type: "none", 213 Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap", "x-snapd.detach"}, 214 }, 215 }) 216 case 2: 217 c.Check(chg, DeepEquals, &update.Change{ 218 Action: update.Unmount, 219 Entry: osutil.MountEntry{ 220 Name: "/usr/share/adduser", Dir: "/usr/share/adduser", Type: "none", 221 Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap", "x-snapd.detach"}, 222 }, 223 }) 224 case 3: 225 c.Check(chg, DeepEquals, &update.Change{ 226 Action: update.Unmount, 227 Entry: osutil.MountEntry{ 228 Name: "tmpfs", Dir: "/usr/share", Type: "tmpfs", 229 Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap", "x-snapd.detach"}, 230 }, 231 }) 232 default: 233 panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg)) 234 } 235 return nil, nil 236 }) 237 defer restore() 238 239 upCtx := update.NewSystemProfileUpdateContext(snapName, false) 240 c.Assert(update.ExecuteMountProfileUpdate(upCtx), IsNil) 241 242 c.Check(currentProfilePath, testutil.FileEquals, "") 243 } 244 245 func (s *mainSuite) TestApplyingLayoutChanges(c *C) { 246 dirs.SetRootDir(c.MkDir()) 247 defer dirs.SetRootDir("/") 248 249 const snapName = "mysnap" 250 const currentProfileContent = "" 251 const desiredProfileContent = "/snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro,x-snapd.origin=layout 0 0" 252 253 currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) 254 desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) 255 256 c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil) 257 c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil) 258 c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil) 259 c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil) 260 261 n := -1 262 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 263 n++ 264 switch n { 265 case 0: 266 c.Assert(chg, DeepEquals, &update.Change{ 267 Action: update.Mount, 268 Entry: osutil.MountEntry{ 269 Name: "/snap/mysnap/42/usr/share/mysnap", 270 Dir: "/usr/share/mysnap", Type: "none", 271 Options: []string{"bind", "ro", "x-snapd.origin=layout"}, 272 }, 273 }) 274 return nil, fmt.Errorf("testing") 275 default: 276 panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg)) 277 } 278 }) 279 defer restore() 280 281 // The error was not ignored, we bailed out. 282 upCtx := update.NewSystemProfileUpdateContext(snapName, false) 283 c.Assert(update.ExecuteMountProfileUpdate(upCtx), ErrorMatches, "testing") 284 285 c.Check(currentProfilePath, testutil.FileEquals, "") 286 } 287 288 func (s *mainSuite) TestApplyingParallelInstanceChanges(c *C) { 289 dirs.SetRootDir(c.MkDir()) 290 defer dirs.SetRootDir("/") 291 292 const snapName = "mysnap" 293 const currentProfileContent = "" 294 const desiredProfileContent = "/snap/mysnap_foo /snap/mysnap none rbind,x-snapd.origin=overname 0 0" 295 296 currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) 297 desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) 298 299 c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil) 300 c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil) 301 c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil) 302 c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil) 303 304 n := -1 305 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 306 n++ 307 switch n { 308 case 0: 309 c.Assert(chg, DeepEquals, &update.Change{ 310 Action: update.Mount, 311 Entry: osutil.MountEntry{ 312 Name: "/snap/mysnap_foo", 313 Dir: "/snap/mysnap", Type: "none", 314 Options: []string{"rbind", "x-snapd.origin=overname"}, 315 }, 316 }) 317 return nil, fmt.Errorf("testing") 318 default: 319 panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg)) 320 } 321 }) 322 defer restore() 323 324 // The error was not ignored, we bailed out. 325 upCtx := update.NewSystemProfileUpdateContext(snapName, false) 326 c.Assert(update.ExecuteMountProfileUpdate(upCtx), ErrorMatches, "testing") 327 328 c.Check(currentProfilePath, testutil.FileEquals, "") 329 } 330 331 func (s *mainSuite) TestApplyIgnoredMissingMount(c *C) { 332 dirs.SetRootDir(c.MkDir()) 333 defer dirs.SetRootDir("/") 334 335 const snapName = "mysnap" 336 const currentProfileContent = "" 337 const desiredProfileContent = "/source /target none bind,x-snapd.ignore-missing 0 0" 338 339 currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) 340 desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) 341 342 c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil) 343 c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil) 344 c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil) 345 c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil) 346 347 n := -1 348 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 349 n++ 350 switch n { 351 case 0: 352 c.Assert(chg, DeepEquals, &update.Change{ 353 Action: update.Mount, 354 Entry: osutil.MountEntry{ 355 Name: "/source", 356 Dir: "/target", 357 Type: "none", 358 Options: []string{"bind", "x-snapd.ignore-missing"}, 359 }, 360 }) 361 return nil, update.ErrIgnoredMissingMount 362 default: 363 panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg)) 364 } 365 }) 366 defer restore() 367 368 // The error was ignored, and no mount was recorded in the profile 369 upCtx := update.NewSystemProfileUpdateContext(snapName, false) 370 c.Assert(update.ExecuteMountProfileUpdate(upCtx), IsNil) 371 c.Check(s.log.String(), Equals, "") 372 c.Check(currentProfilePath, testutil.FileEquals, "") 373 } 374 375 func (s *mainSuite) TestApplyUserFstab(c *C) { 376 dirs.SetRootDir(c.MkDir()) 377 defer dirs.SetRootDir("/") 378 379 var changes []update.Change 380 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 381 changes = append(changes, *chg) 382 return nil, nil 383 }) 384 defer restore() 385 386 snapName := "foo" 387 desiredProfileContent := `$XDG_RUNTIME_DIR/doc/by-app/snap.foo $XDG_RUNTIME_DIR/doc none bind,rw 0 0` 388 389 desiredProfilePath := fmt.Sprintf("%s/snap.%s.user-fstab", dirs.SnapMountPolicyDir, snapName) 390 err := os.MkdirAll(filepath.Dir(desiredProfilePath), 0755) 391 c.Assert(err, IsNil) 392 err = ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644) 393 c.Assert(err, IsNil) 394 395 upCtx := update.NewUserProfileUpdateContext(snapName, true, 1000) 396 err = update.ExecuteMountProfileUpdate(upCtx) 397 c.Assert(err, IsNil) 398 399 xdgRuntimeDir := fmt.Sprintf("%s/%d", dirs.XdgRuntimeDirBase, 1000) 400 401 c.Assert(changes, HasLen, 1) 402 c.Assert(changes[0].Action, Equals, update.Mount) 403 c.Assert(changes[0].Entry.Name, Equals, xdgRuntimeDir+"/doc/by-app/snap.foo") 404 c.Assert(changes[0].Entry.Dir, Matches, xdgRuntimeDir+"/doc") 405 }