gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/mount/spec_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 mount_test 21 22 import ( 23 "strings" 24 25 . "gopkg.in/check.v1" 26 27 "gitee.com/mysnapcore/mysnapd/interfaces" 28 "gitee.com/mysnapcore/mysnapd/interfaces/ifacetest" 29 "gitee.com/mysnapcore/mysnapd/interfaces/mount" 30 "gitee.com/mysnapcore/mysnapd/logger" 31 "gitee.com/mysnapcore/mysnapd/osutil" 32 "gitee.com/mysnapcore/mysnapd/snap" 33 "gitee.com/mysnapcore/mysnapd/snap/snaptest" 34 ) 35 36 type specSuite struct { 37 iface *ifacetest.TestInterface 38 spec *mount.Specification 39 plugInfo *snap.PlugInfo 40 plug *interfaces.ConnectedPlug 41 slotInfo *snap.SlotInfo 42 slot *interfaces.ConnectedSlot 43 } 44 45 var _ = Suite(&specSuite{ 46 iface: &ifacetest.TestInterface{ 47 InterfaceName: "test", 48 MountConnectedPlugCallback: func(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { 49 return spec.AddMountEntry(osutil.MountEntry{Dir: "dir-a", Name: "connected-plug"}) 50 }, 51 MountConnectedSlotCallback: func(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { 52 return spec.AddMountEntry(osutil.MountEntry{Dir: "dir-b", Name: "connected-slot"}) 53 }, 54 MountPermanentPlugCallback: func(spec *mount.Specification, plug *snap.PlugInfo) error { 55 return spec.AddMountEntry(osutil.MountEntry{Dir: "dir-c", Name: "permanent-plug"}) 56 }, 57 MountPermanentSlotCallback: func(spec *mount.Specification, slot *snap.SlotInfo) error { 58 return spec.AddMountEntry(osutil.MountEntry{Dir: "dir-d", Name: "permanent-slot"}) 59 }, 60 }, 61 plugInfo: &snap.PlugInfo{ 62 Snap: &snap.Info{SuggestedName: "snap"}, 63 Name: "name", 64 Interface: "test", 65 }, 66 slotInfo: &snap.SlotInfo{ 67 Snap: &snap.Info{SuggestedName: "snap"}, 68 Name: "name", 69 Interface: "test", 70 }, 71 }) 72 73 func (s *specSuite) SetUpTest(c *C) { 74 s.spec = &mount.Specification{} 75 s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil, nil) 76 s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil, nil) 77 } 78 79 // AddMountEntry and AddUserMountEntry are not not broken 80 func (s *specSuite) TestSmoke(c *C) { 81 ent0 := osutil.MountEntry{Dir: "dir-a", Name: "fs1"} 82 ent1 := osutil.MountEntry{Dir: "dir-b", Name: "fs2"} 83 ent2 := osutil.MountEntry{Dir: "dir-c", Name: "fs3"} 84 85 uent0 := osutil.MountEntry{Dir: "per-user-a", Name: "fs1"} 86 uent1 := osutil.MountEntry{Dir: "per-user-b", Name: "fs2"} 87 88 c.Assert(s.spec.AddMountEntry(ent0), IsNil) 89 c.Assert(s.spec.AddMountEntry(ent1), IsNil) 90 c.Assert(s.spec.AddMountEntry(ent2), IsNil) 91 92 c.Assert(s.spec.AddUserMountEntry(uent0), IsNil) 93 c.Assert(s.spec.AddUserMountEntry(uent1), IsNil) 94 95 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ent0, ent1, ent2}) 96 c.Assert(s.spec.UserMountEntries(), DeepEquals, []osutil.MountEntry{uent0, uent1}) 97 } 98 99 // Added entries can clash and are automatically renamed by MountEntries 100 func (s *specSuite) TestMountEntriesDeclash(c *C) { 101 buf, restore := logger.MockLogger() 102 defer restore() 103 104 c.Assert(s.spec.AddMountEntry(osutil.MountEntry{Dir: "foo", Name: "fs1"}), IsNil) 105 c.Assert(s.spec.AddMountEntry(osutil.MountEntry{Dir: "foo", Name: "fs2"}), IsNil) 106 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 107 {Dir: "foo", Name: "fs1"}, 108 {Dir: "foo-2", Name: "fs2"}, 109 }) 110 111 c.Assert(s.spec.AddUserMountEntry(osutil.MountEntry{Dir: "bar", Name: "fs1"}), IsNil) 112 c.Assert(s.spec.AddUserMountEntry(osutil.MountEntry{Dir: "bar", Name: "fs2"}), IsNil) 113 c.Assert(s.spec.UserMountEntries(), DeepEquals, []osutil.MountEntry{ 114 {Dir: "bar", Name: "fs1"}, 115 {Dir: "bar-2", Name: "fs2"}, 116 }) 117 118 // extract the relevant part of the log 119 loggedMsgs := strings.Split(buf.String(), "\n") 120 msg := strings.SplitAfter(strings.TrimSpace(loggedMsgs[0]), ": ")[1] 121 c.Assert(msg, Equals, `renaming mount entry for directory "foo" to "foo-2" to avoid a clash`) 122 msg = strings.SplitAfter(strings.TrimSpace(loggedMsgs[1]), ": ")[1] 123 c.Assert(msg, Equals, `renaming mount entry for directory "bar" to "bar-2" to avoid a clash`) 124 } 125 126 // The mount.Specification can be used through the interfaces.Specification interface 127 func (s *specSuite) TestSpecificationIface(c *C) { 128 var r interfaces.Specification = s.spec 129 c.Assert(r.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) 130 c.Assert(r.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) 131 c.Assert(r.AddPermanentPlug(s.iface, s.plugInfo), IsNil) 132 c.Assert(r.AddPermanentSlot(s.iface, s.slotInfo), IsNil) 133 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 134 {Dir: "dir-a", Name: "connected-plug"}, 135 {Dir: "dir-b", Name: "connected-slot"}, 136 {Dir: "dir-c", Name: "permanent-plug"}, 137 {Dir: "dir-d", Name: "permanent-slot"}}) 138 } 139 140 const snapWithLayout = ` 141 name: vanguard 142 version: 0 143 layout: 144 /usr: 145 bind: $SNAP/usr 146 /lib/mytmp: 147 type: tmpfs 148 mode: 1777 149 /lib/mylink: 150 symlink: $SNAP/link/target 151 /etc/foo.conf: 152 bind-file: $SNAP/foo.conf 153 ` 154 155 func (s *specSuite) TestMountEntryFromLayout(c *C) { 156 snapInfo := snaptest.MockInfo(c, snapWithLayout, &snap.SideInfo{Revision: snap.R(42)}) 157 s.spec.AddLayout(snapInfo) 158 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 159 // Layout result is sorted by mount path. 160 {Dir: "/etc/foo.conf", Name: "/snap/vanguard/42/foo.conf", Options: []string{"bind", "rw", "x-snapd.kind=file", "x-snapd.origin=layout"}}, 161 {Dir: "/lib/mylink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/vanguard/42/link/target", "x-snapd.origin=layout"}}, 162 {Dir: "/lib/mytmp", Name: "tmpfs", Type: "tmpfs", Options: []string{"x-snapd.mode=01777", "x-snapd.origin=layout"}}, 163 {Dir: "/usr", Name: "/snap/vanguard/42/usr", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, 164 }) 165 } 166 167 func (s *specSuite) TestMountEntryFromExtraLayouts(c *C) { 168 extraLayouts := []snap.Layout{ 169 { 170 Path: "/test", 171 Bind: "/usr/home/test", 172 Mode: 0755, 173 }, 174 } 175 176 s.spec.AddExtraLayouts(extraLayouts) 177 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 178 {Dir: "/test", Name: "/usr/home/test", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, 179 }) 180 } 181 182 func (s *specSuite) TestParallelInstanceMountEntryFromLayout(c *C) { 183 snapInfo := snaptest.MockInfo(c, snapWithLayout, &snap.SideInfo{Revision: snap.R(42)}) 184 snapInfo.InstanceKey = "instance" 185 s.spec.AddLayout(snapInfo) 186 s.spec.AddOvername(snapInfo) 187 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 188 // Parallel instance mappings come first 189 {Dir: "/snap/vanguard", Name: "/snap/vanguard_instance", Options: []string{"rbind", "x-snapd.origin=overname"}}, 190 {Dir: "/var/snap/vanguard", Name: "/var/snap/vanguard_instance", Options: []string{"rbind", "x-snapd.origin=overname"}}, 191 // Layout result is sorted by mount path. 192 {Dir: "/etc/foo.conf", Name: "/snap/vanguard/42/foo.conf", Options: []string{"bind", "rw", "x-snapd.kind=file", "x-snapd.origin=layout"}}, 193 {Dir: "/lib/mylink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/vanguard/42/link/target", "x-snapd.origin=layout"}}, 194 {Dir: "/lib/mytmp", Name: "tmpfs", Type: "tmpfs", Options: []string{"x-snapd.mode=01777", "x-snapd.origin=layout"}}, 195 {Dir: "/usr", Name: "/snap/vanguard/42/usr", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, 196 }) 197 } 198 199 func (s *specSuite) TestSpecificationUberclash(c *C) { 200 // When everything clashes for access to /usr/foo, what happens? 201 const uberclashYaml = `name: uberclash 202 version: 0 203 layout: 204 /usr/foo: 205 type: tmpfs 206 ` 207 snapInfo := snaptest.MockInfo(c, uberclashYaml, &snap.SideInfo{Revision: snap.R(42)}) 208 entry := osutil.MountEntry{Dir: "/usr/foo", Type: "tmpfs", Name: "tmpfs"} 209 s.spec.AddMountEntry(entry) 210 s.spec.AddUserMountEntry(entry) 211 s.spec.AddLayout(snapInfo) 212 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 213 {Dir: "/usr/foo", Type: "tmpfs", Name: "tmpfs", Options: []string{"x-snapd.origin=layout"}}, 214 // This is the non-layout entry, it was renamed to "foo-2" 215 {Dir: "/usr/foo-2", Type: "tmpfs", Name: "tmpfs"}, 216 }) 217 c.Assert(s.spec.UserMountEntries(), DeepEquals, []osutil.MountEntry{ 218 // This is the user entry, it was _not_ renamed and it would clash with 219 // /foo but there is no way to request things like that for now. 220 {Dir: "/usr/foo", Type: "tmpfs", Name: "tmpfs"}, 221 }) 222 } 223 224 func (s *specSuite) TestSpecificationMergedClash(c *C) { 225 defaultEntry := osutil.MountEntry{ 226 Dir: "/usr/foo", 227 Type: "tmpfs", 228 Name: "/here", 229 } 230 for _, td := range []struct { 231 // Options for all the clashing mount entries 232 Options [][]string 233 // Expected options for the merged mount entry 234 ExpectedOptions []string 235 }{ 236 { 237 // If all entries are read-only, the merged entry is also RO 238 Options: [][]string{{"noatime", "ro"}, {"ro"}}, 239 ExpectedOptions: []string{"noatime", "ro"}, 240 }, 241 { 242 // If one entry is rbind, the recursiveness is preserved 243 Options: [][]string{{"bind", "rw"}, {"rbind", "ro"}}, 244 ExpectedOptions: []string{"rbind"}, 245 }, 246 { 247 // With simple bind, no recursiveness is added 248 Options: [][]string{{"bind", "noatime"}, {"bind", "noexec"}}, 249 ExpectedOptions: []string{"noatime", "noexec", "bind"}, 250 }, 251 { 252 // Ordinary flags are preserved 253 Options: [][]string{{"noexec", "noatime"}, {"noatime", "nomand"}, {"nodev"}}, 254 ExpectedOptions: []string{"noexec", "noatime", "nomand", "nodev"}, 255 }, 256 } { 257 for _, options := range td.Options { 258 entry := defaultEntry 259 entry.Options = options 260 s.spec.AddMountEntry(entry) 261 } 262 c.Check(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 263 {Dir: "/usr/foo", Name: "/here", Type: "tmpfs", Options: td.ExpectedOptions}, 264 }, Commentf("Clashing entries: %q", td.Options)) 265 266 // reset the spec after each iteration, or flags will leak 267 s.spec = &mount.Specification{} 268 } 269 } 270 271 func (s *specSuite) TestParallelInstanceMountEntriesNoInstanceKey(c *C) { 272 snapInfo := &snap.Info{SideInfo: snap.SideInfo{RealName: "foo", Revision: snap.R(42)}} 273 s.spec.AddOvername(snapInfo) 274 c.Assert(s.spec.MountEntries(), HasLen, 0) 275 c.Assert(s.spec.UserMountEntries(), HasLen, 0) 276 } 277 278 func (s *specSuite) TestParallelInstanceMountEntriesReal(c *C) { 279 snapInfo := &snap.Info{SideInfo: snap.SideInfo{RealName: "foo", Revision: snap.R(42)}, InstanceKey: "instance"} 280 s.spec.AddOvername(snapInfo) 281 c.Assert(s.spec.MountEntries(), DeepEquals, []osutil.MountEntry{ 282 // /snap/foo_instance -> /snap/foo 283 {Name: "/snap/foo_instance", Dir: "/snap/foo", Options: []string{"rbind", "x-snapd.origin=overname"}}, 284 // /var/snap/foo_instance -> /var/snap/foo 285 {Name: "/var/snap/foo_instance", Dir: "/var/snap/foo", Options: []string{"rbind", "x-snapd.origin=overname"}}, 286 }) 287 c.Assert(s.spec.UserMountEntries(), HasLen, 0) 288 }