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