github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/storage/provider/rootfs_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provider_test 5 6 import ( 7 "errors" 8 "path/filepath" 9 10 "github.com/juju/names/v5" 11 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 14 "github.com/juju/juju/environs/context" 15 "github.com/juju/juju/storage" 16 "github.com/juju/juju/storage/provider" 17 "github.com/juju/juju/testing" 18 ) 19 20 var _ = gc.Suite(&rootfsSuite{}) 21 22 type rootfsSuite struct { 23 testing.BaseSuite 24 storageDir string 25 commands *mockRunCommand 26 mockDirFuncs *provider.MockDirFuncs 27 fakeEtcDir string 28 29 callCtx context.ProviderCallContext 30 } 31 32 func (s *rootfsSuite) SetUpTest(c *gc.C) { 33 s.BaseSuite.SetUpTest(c) 34 s.storageDir = c.MkDir() 35 s.fakeEtcDir = c.MkDir() 36 s.callCtx = context.NewEmptyCloudCallContext() 37 } 38 39 func (s *rootfsSuite) TearDownTest(c *gc.C) { 40 if s.commands != nil { 41 s.commands.assertDrained() 42 } 43 s.BaseSuite.TearDownTest(c) 44 } 45 46 func (s *rootfsSuite) rootfsProvider(c *gc.C) storage.Provider { 47 s.commands = &mockRunCommand{c: c} 48 return provider.RootfsProvider(s.commands.run) 49 } 50 51 func (s *rootfsSuite) TestFilesystemSource(c *gc.C) { 52 p := s.rootfsProvider(c) 53 cfg, err := storage.NewConfig("name", provider.RootfsProviderType, map[string]interface{}{}) 54 c.Assert(err, jc.ErrorIsNil) 55 _, err = p.FilesystemSource(cfg) 56 c.Assert(err, gc.ErrorMatches, "storage directory not specified") 57 cfg, err = storage.NewConfig("name", provider.RootfsProviderType, map[string]interface{}{ 58 "storage-dir": c.MkDir(), 59 }) 60 c.Assert(err, jc.ErrorIsNil) 61 _, err = p.FilesystemSource(cfg) 62 c.Assert(err, jc.ErrorIsNil) 63 } 64 65 func (s *rootfsSuite) TestValidateConfig(c *gc.C) { 66 p := s.rootfsProvider(c) 67 cfg, err := storage.NewConfig("name", provider.RootfsProviderType, map[string]interface{}{}) 68 c.Assert(err, jc.ErrorIsNil) 69 err = p.ValidateConfig(cfg) 70 // The rootfs provider does not have any user 71 // configuration, so an empty map will pass. 72 c.Assert(err, jc.ErrorIsNil) 73 } 74 75 func (s *rootfsSuite) TestSupports(c *gc.C) { 76 p := s.rootfsProvider(c) 77 c.Assert(p.Supports(storage.StorageKindBlock), jc.IsFalse) 78 c.Assert(p.Supports(storage.StorageKindFilesystem), jc.IsTrue) 79 } 80 81 func (s *rootfsSuite) TestScope(c *gc.C) { 82 p := s.rootfsProvider(c) 83 c.Assert(p.Scope(), gc.Equals, storage.ScopeMachine) 84 } 85 86 func (s *rootfsSuite) rootfsFilesystemSource(c *gc.C, fakeMountInfo ...string) storage.FilesystemSource { 87 s.commands = &mockRunCommand{c: c} 88 source, d := provider.RootfsFilesystemSource(s.fakeEtcDir, s.storageDir, s.commands.run, fakeMountInfo...) 89 s.mockDirFuncs = d 90 return source 91 } 92 93 func (s *rootfsSuite) TestCreateFilesystems(c *gc.C) { 94 source := s.rootfsFilesystemSource(c) 95 cmd := s.commands.expect("df", "--output=size", s.storageDir) 96 cmd.respond("1K-blocks\n2048", nil) 97 cmd = s.commands.expect("df", "--output=size", s.storageDir) 98 cmd.respond("1K-blocks\n4096", nil) 99 100 results, err := source.CreateFilesystems(s.callCtx, []storage.FilesystemParams{{ 101 Tag: names.NewFilesystemTag("6"), 102 Size: 2, 103 }, { 104 Tag: names.NewFilesystemTag("7"), 105 Size: 4, 106 }}) 107 c.Assert(err, jc.ErrorIsNil) 108 109 c.Assert(results, jc.DeepEquals, []storage.CreateFilesystemsResult{{ 110 Filesystem: &storage.Filesystem{ 111 Tag: names.NewFilesystemTag("6"), 112 FilesystemInfo: storage.FilesystemInfo{ 113 FilesystemId: "6", 114 Size: 2, 115 }, 116 }, 117 }, { 118 Filesystem: &storage.Filesystem{ 119 Tag: names.NewFilesystemTag("7"), 120 FilesystemInfo: storage.FilesystemInfo{ 121 FilesystemId: "7", 122 Size: 4, 123 }, 124 }, 125 }}) 126 } 127 128 func (s *rootfsSuite) TestCreateFilesystemsIsUse(c *gc.C) { 129 source := s.rootfsFilesystemSource(c) 130 results, err := source.CreateFilesystems(s.callCtx, []storage.FilesystemParams{{ 131 Tag: names.NewFilesystemTag("666"), // magic; see mockDirFuncs 132 Size: 1, 133 }}) 134 c.Assert(err, jc.ErrorIsNil) 135 c.Assert(results[0].Error, gc.ErrorMatches, "\".*/666\" is not empty") 136 } 137 138 func (s *rootfsSuite) TestAttachFilesystemsPathNotDir(c *gc.C) { 139 source := s.rootfsFilesystemSource(c) 140 results, err := source.AttachFilesystems(s.callCtx, []storage.FilesystemAttachmentParams{{ 141 Filesystem: names.NewFilesystemTag("6"), 142 FilesystemId: "6", 143 Path: "file", 144 }}) 145 c.Assert(err, jc.ErrorIsNil) 146 c.Assert(results[0].Error, gc.ErrorMatches, `path "file" must be a directory`) 147 } 148 149 func (s *rootfsSuite) TestCreateFilesystemsNotEnoughSpace(c *gc.C) { 150 source := s.rootfsFilesystemSource(c) 151 cmd := s.commands.expect("df", "--output=size", s.storageDir) 152 cmd.respond("1K-blocks\n2048", nil) 153 154 results, err := source.CreateFilesystems(s.callCtx, []storage.FilesystemParams{{ 155 Tag: names.NewFilesystemTag("6"), 156 Size: 4, 157 }}) 158 c.Assert(err, jc.ErrorIsNil) 159 c.Assert(results[0].Error, gc.ErrorMatches, "filesystem is not big enough \\(2M < 4M\\)") 160 } 161 162 func (s *rootfsSuite) TestCreateFilesystemsInvalidPath(c *gc.C) { 163 source := s.rootfsFilesystemSource(c) 164 cmd := s.commands.expect("df", "--output=size", s.storageDir) 165 cmd.respond("", errors.New("error creating directory")) 166 167 results, err := source.CreateFilesystems(s.callCtx, []storage.FilesystemParams{{ 168 Tag: names.NewFilesystemTag("6"), 169 Size: 2, 170 }}) 171 c.Assert(err, jc.ErrorIsNil) 172 c.Assert(results[0].Error, gc.ErrorMatches, "getting size: error creating directory") 173 } 174 175 func (s *rootfsSuite) TestAttachFilesystemsNoPathSpecified(c *gc.C) { 176 source := s.rootfsFilesystemSource(c) 177 results, err := source.AttachFilesystems(s.callCtx, []storage.FilesystemAttachmentParams{{ 178 Filesystem: names.NewFilesystemTag("6"), 179 FilesystemId: "6", 180 }}) 181 c.Assert(err, jc.ErrorIsNil) 182 c.Assert(results[0].Error, gc.ErrorMatches, "filesystem mount point not specified") 183 } 184 185 func (s *rootfsSuite) TestAttachFilesystemsBind(c *gc.C) { 186 source := s.rootfsFilesystemSource(c) 187 188 cmd := s.commands.expect("mount", "--bind", filepath.Join(s.storageDir, "6"), "/srv") 189 cmd.respond("", nil) 190 191 results, err := source.AttachFilesystems(s.callCtx, []storage.FilesystemAttachmentParams{{ 192 Filesystem: names.NewFilesystemTag("6"), 193 FilesystemId: "6", 194 Path: "/srv", 195 }}) 196 c.Assert(err, jc.ErrorIsNil) 197 c.Assert(results, jc.DeepEquals, []storage.AttachFilesystemsResult{{ 198 FilesystemAttachment: &storage.FilesystemAttachment{ 199 Filesystem: names.NewFilesystemTag("6"), 200 FilesystemAttachmentInfo: storage.FilesystemAttachmentInfo{ 201 Path: "/srv", 202 }, 203 }, 204 }}) 205 } 206 207 func (s *rootfsSuite) TestAttachFilesystemsBound(c *gc.C) { 208 // already bind-mounted storage-dir/6 to the target 209 mountInfo := mountInfoLine(666, 0, filepath.Join(s.storageDir, "6"), "/srv", "/dev/sda1") 210 source := s.rootfsFilesystemSource(c, mountInfo) 211 212 results, err := source.AttachFilesystems(s.callCtx, []storage.FilesystemAttachmentParams{{ 213 Filesystem: names.NewFilesystemTag("6"), 214 FilesystemId: "6", 215 Path: "/srv", 216 }}) 217 c.Assert(err, jc.ErrorIsNil) 218 c.Assert(results, jc.DeepEquals, []storage.AttachFilesystemsResult{{ 219 FilesystemAttachment: &storage.FilesystemAttachment{ 220 Filesystem: names.NewFilesystemTag("6"), 221 FilesystemAttachmentInfo: storage.FilesystemAttachmentInfo{ 222 Path: "/srv", 223 }, 224 }, 225 }}) 226 } 227 228 func (s *rootfsSuite) TestAttachFilesystemsBoundViaParent(c *gc.C) { 229 mountInfo1 := mountInfoLine(666, 667, filepath.Join("/some/parent/path", s.storageDir, "6"), "/srv", "/dev/sda1") 230 mountInfo2 := mountInfoLine(667, 668, "/some/parent/path", "/", "/dev/sda1") 231 source := s.rootfsFilesystemSource(c, mountInfo1, mountInfo2) 232 233 results, err := source.AttachFilesystems(s.callCtx, []storage.FilesystemAttachmentParams{{ 234 Filesystem: names.NewFilesystemTag("6"), 235 FilesystemId: "6", 236 Path: "/srv", 237 }}) 238 c.Assert(err, jc.ErrorIsNil) 239 c.Assert(results, jc.DeepEquals, []storage.AttachFilesystemsResult{{ 240 FilesystemAttachment: &storage.FilesystemAttachment{ 241 Filesystem: names.NewFilesystemTag("6"), 242 FilesystemAttachmentInfo: storage.FilesystemAttachmentInfo{ 243 Path: "/srv", 244 }, 245 }, 246 }}) 247 } 248 249 func (s *rootfsSuite) TestAttachFilesystemsBoundViaMultipleParents(c *gc.C) { 250 mountInfo1 := mountInfoLine(666, 667, filepath.Join("/some/parent/path", s.storageDir, "6"), "/srv", "/dev/sda1") 251 mountInfo2 := mountInfoLine(667, 668, "/some/parent/path", "/another/parent/path", "/dev/sda1") 252 mountInfo3 := mountInfoLine(668, 669, "/another/parent/path", "/", "/dev/sda1") 253 source := s.rootfsFilesystemSource(c, mountInfo1, mountInfo2, mountInfo3) 254 255 results, err := source.AttachFilesystems(s.callCtx, []storage.FilesystemAttachmentParams{{ 256 Filesystem: names.NewFilesystemTag("6"), 257 FilesystemId: "6", 258 Path: "/srv", 259 }}) 260 c.Assert(err, jc.ErrorIsNil) 261 c.Assert(results, jc.DeepEquals, []storage.AttachFilesystemsResult{{ 262 FilesystemAttachment: &storage.FilesystemAttachment{ 263 Filesystem: names.NewFilesystemTag("6"), 264 FilesystemAttachmentInfo: storage.FilesystemAttachmentInfo{ 265 Path: "/srv", 266 }, 267 }, 268 }}) 269 } 270 271 func (s *rootfsSuite) TestAttachFilesystemsBindFailsDifferentFS(c *gc.C) { 272 mountInfo1 := mountInfoLine(666, 0, "/somewhere", filepath.Join(s.storageDir, "6"), "/dev") 273 mountInfo2 := mountInfoLine(667, 0, "/src/of/root", "/srv", "/proc") 274 source := s.rootfsFilesystemSource(c, mountInfo1, mountInfo2) 275 276 cmd := s.commands.expect("mount", "--bind", filepath.Join(s.storageDir, "6"), "/srv") 277 cmd.respond("", errors.New("mount --bind fails")) 278 279 results, err := source.AttachFilesystems(s.callCtx, []storage.FilesystemAttachmentParams{{ 280 Filesystem: names.NewFilesystemTag("6"), 281 FilesystemId: "6", 282 Path: "/srv", 283 }}) 284 c.Assert(err, jc.ErrorIsNil) 285 c.Assert(results[0].Error, gc.ErrorMatches, `".*/6" \("/dev"\) and "/srv" \("/proc"\) are on different filesystems`) 286 } 287 288 func (s *rootfsSuite) TestAttachFilesystemsBindSameFSEmptyDir(c *gc.C) { 289 mountInfo1 := mountInfoLine(666, 0, "/somewhere", filepath.Join(s.storageDir, "6"), "/dev") 290 mountInfo2 := mountInfoLine(667, 0, "/src/of/root", "/srv", "/dev") 291 source := s.rootfsFilesystemSource(c, mountInfo1, mountInfo2) 292 293 cmd := s.commands.expect("mount", "--bind", filepath.Join(s.storageDir, "6"), "/srv") 294 cmd.respond("", errors.New("mount --bind fails")) 295 296 results, err := source.AttachFilesystems(s.callCtx, []storage.FilesystemAttachmentParams{{ 297 Filesystem: names.NewFilesystemTag("6"), 298 FilesystemId: "6", 299 Path: "/srv", 300 }}) 301 c.Assert(err, jc.ErrorIsNil) 302 c.Assert(results, jc.DeepEquals, []storage.AttachFilesystemsResult{{ 303 FilesystemAttachment: &storage.FilesystemAttachment{ 304 Filesystem: names.NewFilesystemTag("6"), 305 FilesystemAttachmentInfo: storage.FilesystemAttachmentInfo{ 306 Path: "/srv", 307 }, 308 }, 309 }}) 310 } 311 312 func (s *rootfsSuite) TestAttachFilesystemsBindSameFSNonEmptyDirUnclaimed(c *gc.C) { 313 mountInfo1 := mountInfoLine(666, 0, "/somewhere", filepath.Join(s.storageDir, "6"), "/dev") 314 mountInfo2 := mountInfoLine(667, 0, "/src/of/root", "/srv/666", "/dev") 315 source := s.rootfsFilesystemSource(c, mountInfo1, mountInfo2) 316 317 cmd := s.commands.expect("mount", "--bind", filepath.Join(s.storageDir, "6"), "/srv/666") 318 cmd.respond("", errors.New("mount --bind fails")) 319 320 results, err := source.AttachFilesystems(s.callCtx, []storage.FilesystemAttachmentParams{{ 321 Filesystem: names.NewFilesystemTag("6"), 322 FilesystemId: "6", 323 Path: "/srv/666", 324 }}) 325 c.Assert(err, jc.ErrorIsNil) 326 c.Assert(results[0].Error, gc.ErrorMatches, `"/srv/666" is not empty`) 327 } 328 329 func (s *rootfsSuite) TestAttachFilesystemsBindSameFSNonEmptyDirClaimed(c *gc.C) { 330 mountInfo1 := mountInfoLine(666, 0, "/somewhere", filepath.Join(s.storageDir, "6"), "/dev") 331 mountInfo2 := mountInfoLine(667, 0, "/src/of/root", "/srv/666", "/dev") 332 source := s.rootfsFilesystemSource(c, mountInfo1, mountInfo2) 333 334 cmd := s.commands.expect("mount", "--bind", filepath.Join(s.storageDir, "6"), "/srv/666") 335 cmd.respond("", errors.New("mount --bind fails")) 336 337 s.mockDirFuncs.Dirs.Add(filepath.Join(s.storageDir, "6", "juju-target-claimed")) 338 339 results, err := source.AttachFilesystems(s.callCtx, []storage.FilesystemAttachmentParams{{ 340 Filesystem: names.NewFilesystemTag("6"), 341 FilesystemId: "6", 342 Path: "/srv/666", 343 }}) 344 c.Assert(err, jc.ErrorIsNil) 345 c.Assert(results, jc.DeepEquals, []storage.AttachFilesystemsResult{{ 346 FilesystemAttachment: &storage.FilesystemAttachment{ 347 Filesystem: names.NewFilesystemTag("6"), 348 FilesystemAttachmentInfo: storage.FilesystemAttachmentInfo{ 349 Path: "/srv/666", 350 }, 351 }, 352 }}) 353 } 354 355 func (s *rootfsSuite) TestDetachFilesystems(c *gc.C) { 356 mountInfo := mountInfoLine(666, 0, "/src/of/root", testMountPoint, "/dev/sda1") 357 source := s.rootfsFilesystemSource(c, mountInfo) 358 testDetachFilesystems(c, s.commands, source, s.callCtx, true, s.fakeEtcDir, "") 359 } 360 361 func (s *rootfsSuite) TestDetachFilesystemsUnattached(c *gc.C) { 362 // The "unattached" case covers both idempotency, and 363 // also the scenario where bind-mounting failed. In 364 // either case, there is no attachment-specific filesystem 365 // mount. 366 source := s.rootfsFilesystemSource(c) 367 testDetachFilesystems(c, s.commands, source, s.callCtx, false, s.fakeEtcDir, "") 368 }