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