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  }