github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/storage/provider/loop_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  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names/v5"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    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(&loopSuite{})
    22  
    23  type loopSuite struct {
    24  	testing.BaseSuite
    25  	storageDir string
    26  	commands   *mockRunCommand
    27  
    28  	callCtx context.ProviderCallContext
    29  }
    30  
    31  func (s *loopSuite) SetUpTest(c *gc.C) {
    32  	s.BaseSuite.SetUpTest(c)
    33  	s.storageDir = c.MkDir()
    34  	s.callCtx = context.NewEmptyCloudCallContext()
    35  }
    36  
    37  func (s *loopSuite) TearDownTest(c *gc.C) {
    38  	s.commands.assertDrained()
    39  	s.BaseSuite.TearDownTest(c)
    40  }
    41  
    42  func (s *loopSuite) loopProvider(c *gc.C) storage.Provider {
    43  	s.commands = &mockRunCommand{c: c}
    44  	return provider.LoopProvider(s.commands.run)
    45  }
    46  
    47  func (s *loopSuite) TestVolumeSource(c *gc.C) {
    48  	p := s.loopProvider(c)
    49  	cfg, err := storage.NewConfig("name", provider.LoopProviderType, map[string]interface{}{})
    50  	c.Assert(err, jc.ErrorIsNil)
    51  	_, err = p.VolumeSource(cfg)
    52  	c.Assert(err, gc.ErrorMatches, "storage directory not specified")
    53  	cfg, err = storage.NewConfig("name", provider.LoopProviderType, map[string]interface{}{
    54  		"storage-dir": c.MkDir(),
    55  	})
    56  	c.Assert(err, jc.ErrorIsNil)
    57  	_, err = p.VolumeSource(cfg)
    58  	c.Assert(err, jc.ErrorIsNil)
    59  }
    60  
    61  func (s *loopSuite) TestValidateConfig(c *gc.C) {
    62  	p := s.loopProvider(c)
    63  	cfg, err := storage.NewConfig("name", provider.LoopProviderType, map[string]interface{}{})
    64  	c.Assert(err, jc.ErrorIsNil)
    65  	err = p.ValidateConfig(cfg)
    66  	// The loop provider does not have any user
    67  	// configuration, so an empty map will pass.
    68  	c.Assert(err, jc.ErrorIsNil)
    69  }
    70  
    71  func (s *loopSuite) TestSupports(c *gc.C) {
    72  	p := s.loopProvider(c)
    73  	c.Assert(p.Supports(storage.StorageKindBlock), jc.IsTrue)
    74  	c.Assert(p.Supports(storage.StorageKindFilesystem), jc.IsFalse)
    75  }
    76  
    77  func (s *loopSuite) TestScope(c *gc.C) {
    78  	p := s.loopProvider(c)
    79  	c.Assert(p.Scope(), gc.Equals, storage.ScopeMachine)
    80  }
    81  
    82  func (s *loopSuite) loopVolumeSource(c *gc.C) (storage.VolumeSource, *provider.MockDirFuncs) {
    83  	s.commands = &mockRunCommand{c: c}
    84  	return provider.LoopVolumeSource(
    85  		c.MkDir(),
    86  		s.storageDir,
    87  		s.commands.run,
    88  	)
    89  }
    90  
    91  func (s *loopSuite) TestCreateVolumes(c *gc.C) {
    92  	source, _ := s.loopVolumeSource(c)
    93  	s.commands.expect("fallocate", "-l", "2MiB", filepath.Join(s.storageDir, "volume-0"))
    94  
    95  	results, err := source.CreateVolumes(s.callCtx, []storage.VolumeParams{{
    96  		Tag:  names.NewVolumeTag("0"),
    97  		Size: 2,
    98  		Attachment: &storage.VolumeAttachmentParams{
    99  			AttachmentParams: storage.AttachmentParams{
   100  				Machine:    names.NewMachineTag("1"),
   101  				InstanceId: "instance-id",
   102  			},
   103  		},
   104  	}})
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	c.Assert(results, gc.HasLen, 1)
   107  	c.Assert(results[0].Error, jc.ErrorIsNil)
   108  	// volume attachments always deferred to AttachVolumes
   109  	c.Assert(results[0].VolumeAttachment, gc.IsNil)
   110  	c.Assert(results[0].Volume, jc.DeepEquals, &storage.Volume{
   111  		names.NewVolumeTag("0"),
   112  		storage.VolumeInfo{
   113  			VolumeId: "volume-0",
   114  			Size:     2,
   115  		},
   116  	})
   117  }
   118  
   119  func (s *loopSuite) TestCreateVolumesNoAttachment(c *gc.C) {
   120  	source, _ := s.loopVolumeSource(c)
   121  	s.commands.expect("fallocate", "-l", "2MiB", filepath.Join(s.storageDir, "volume-0"))
   122  	_, err := source.CreateVolumes(s.callCtx, []storage.VolumeParams{{
   123  		Tag:  names.NewVolumeTag("0"),
   124  		Size: 2,
   125  	}})
   126  	// loop volumes may be created without attachments
   127  	c.Assert(err, jc.ErrorIsNil)
   128  }
   129  
   130  func (s *loopSuite) TestDestroyVolumes(c *gc.C) {
   131  	source, _ := s.loopVolumeSource(c)
   132  	fileName := filepath.Join(s.storageDir, "volume-0")
   133  
   134  	err := os.WriteFile(fileName, nil, 0644)
   135  	c.Assert(err, jc.ErrorIsNil)
   136  
   137  	errs, err := source.DestroyVolumes(s.callCtx, []string{"volume-0"})
   138  	c.Assert(err, jc.ErrorIsNil)
   139  	c.Assert(errs, gc.HasLen, 1)
   140  	c.Assert(errs[0], jc.ErrorIsNil)
   141  
   142  	_, err = os.Stat(fileName)
   143  	c.Assert(err, jc.Satisfies, os.IsNotExist)
   144  }
   145  
   146  func (s *loopSuite) TestDestroyVolumesInvalidVolumeId(c *gc.C) {
   147  	source, _ := s.loopVolumeSource(c)
   148  	errs, err := source.DestroyVolumes(s.callCtx, []string{"../super/important/stuff"})
   149  	c.Assert(err, jc.ErrorIsNil)
   150  	c.Assert(errs, gc.HasLen, 1)
   151  	c.Assert(errs[0], gc.ErrorMatches, `.* invalid loop volume ID "\.\./super/important/stuff"`)
   152  }
   153  
   154  func (s *loopSuite) TestDescribeVolumes(c *gc.C) {
   155  	source, _ := s.loopVolumeSource(c)
   156  	_, err := source.DescribeVolumes(s.callCtx, []string{"a", "b"})
   157  	c.Assert(err, jc.Satisfies, errors.IsNotImplemented)
   158  }
   159  
   160  func (s *loopSuite) TestAttachVolumes(c *gc.C) {
   161  	source, _ := s.loopVolumeSource(c)
   162  	cmd := s.commands.expect("losetup", "-j", filepath.Join(s.storageDir, "volume-0"))
   163  	cmd.respond("", nil) // no existing attachment
   164  	cmd = s.commands.expect("losetup", "-f", "--show", filepath.Join(s.storageDir, "volume-0"))
   165  	cmd.respond("/dev/loop98", nil) // first available loop device
   166  	cmd = s.commands.expect("losetup", "-j", filepath.Join(s.storageDir, "volume-1"))
   167  	cmd.respond("", nil) // no existing attachment
   168  	cmd = s.commands.expect("losetup", "-f", "--show", "-r", filepath.Join(s.storageDir, "volume-1"))
   169  	cmd.respond("/dev/loop99", nil)
   170  	cmd = s.commands.expect("losetup", "-j", filepath.Join(s.storageDir, "volume-2"))
   171  	cmd.respond("/dev/loop42: foo\n/dev/loop1: foo\n", nil) // existing attachments
   172  
   173  	results, err := source.AttachVolumes(s.callCtx, []storage.VolumeAttachmentParams{{
   174  		Volume:   names.NewVolumeTag("0"),
   175  		VolumeId: "vol-ume0",
   176  		AttachmentParams: storage.AttachmentParams{
   177  			Machine:    names.NewMachineTag("0"),
   178  			InstanceId: "inst-ance",
   179  		},
   180  	}, {
   181  		Volume:   names.NewVolumeTag("1"),
   182  		VolumeId: "vol-ume1",
   183  		AttachmentParams: storage.AttachmentParams{
   184  			Machine:    names.NewMachineTag("0"),
   185  			InstanceId: "inst-ance",
   186  			ReadOnly:   true,
   187  		},
   188  	}, {
   189  		Volume:   names.NewVolumeTag("2"),
   190  		VolumeId: "vol-ume2",
   191  		AttachmentParams: storage.AttachmentParams{
   192  			Machine:    names.NewMachineTag("0"),
   193  			InstanceId: "inst-ance",
   194  		},
   195  	}})
   196  	c.Assert(err, jc.ErrorIsNil)
   197  	c.Assert(results, jc.DeepEquals, []storage.AttachVolumesResult{{
   198  		VolumeAttachment: &storage.VolumeAttachment{names.NewVolumeTag("0"),
   199  			names.NewMachineTag("0"),
   200  			storage.VolumeAttachmentInfo{
   201  				DeviceName: "loop98",
   202  			},
   203  		},
   204  	}, {
   205  		VolumeAttachment: &storage.VolumeAttachment{names.NewVolumeTag("1"),
   206  			names.NewMachineTag("0"),
   207  			storage.VolumeAttachmentInfo{
   208  				DeviceName: "loop99",
   209  				ReadOnly:   true,
   210  			},
   211  		},
   212  	}, {
   213  		VolumeAttachment: &storage.VolumeAttachment{names.NewVolumeTag("2"),
   214  			names.NewMachineTag("0"),
   215  			storage.VolumeAttachmentInfo{
   216  				DeviceName: "loop42",
   217  			},
   218  		},
   219  	}})
   220  }
   221  
   222  func (s *loopSuite) TestDetachVolumes(c *gc.C) {
   223  	source, _ := s.loopVolumeSource(c)
   224  	fileName := filepath.Join(s.storageDir, "volume-0")
   225  	cmd := s.commands.expect("losetup", "-j", fileName)
   226  	cmd.respond("/dev/loop0: foo\n/dev/loop1: bar\n", nil)
   227  	s.commands.expect("losetup", "-d", "/dev/loop0")
   228  	s.commands.expect("losetup", "-d", "/dev/loop1")
   229  
   230  	err := os.WriteFile(fileName, nil, 0644)
   231  	c.Assert(err, jc.ErrorIsNil)
   232  
   233  	errs, err := source.DetachVolumes(s.callCtx, []storage.VolumeAttachmentParams{{
   234  		Volume:   names.NewVolumeTag("0"),
   235  		VolumeId: "vol-ume0",
   236  		AttachmentParams: storage.AttachmentParams{
   237  			Machine:    names.NewMachineTag("0"),
   238  			InstanceId: "inst-ance",
   239  		},
   240  	}})
   241  	c.Assert(err, jc.ErrorIsNil)
   242  	c.Assert(errs, gc.HasLen, 1)
   243  	c.Assert(errs[0], jc.ErrorIsNil)
   244  
   245  	// file should not have been removed
   246  	_, err = os.Stat(fileName)
   247  	c.Assert(err, jc.ErrorIsNil)
   248  }
   249  
   250  func (s *loopSuite) TestDetachVolumesDetachFails(c *gc.C) {
   251  	source, _ := s.loopVolumeSource(c)
   252  	fileName := filepath.Join(s.storageDir, "volume-0")
   253  	cmd := s.commands.expect("losetup", "-j", fileName)
   254  	cmd.respond("/dev/loop0: foo\n/dev/loop1: bar\n", nil)
   255  	cmd = s.commands.expect("losetup", "-d", "/dev/loop0")
   256  	cmd.respond("", errors.New("oy"))
   257  
   258  	err := os.WriteFile(fileName, nil, 0644)
   259  	c.Assert(err, jc.ErrorIsNil)
   260  
   261  	errs, err := source.DetachVolumes(s.callCtx, []storage.VolumeAttachmentParams{{
   262  		Volume:   names.NewVolumeTag("0"),
   263  		VolumeId: "vol-ume0",
   264  		AttachmentParams: storage.AttachmentParams{
   265  			Machine:    names.NewMachineTag("0"),
   266  			InstanceId: "inst-ance",
   267  		},
   268  	}})
   269  	c.Assert(err, jc.ErrorIsNil)
   270  	c.Assert(errs, gc.HasLen, 1)
   271  	c.Assert(errs[0], gc.ErrorMatches, `.* detaching loop device "loop0": oy`)
   272  
   273  	// file should not have been removed
   274  	_, err = os.Stat(fileName)
   275  	c.Assert(err, jc.ErrorIsNil)
   276  }