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