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  }