github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/featuretests/storage_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package featuretests
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/cmd"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	"github.com/juju/juju/cmd/envcmd"
    16  	cmdstorage "github.com/juju/juju/cmd/juju/storage"
    17  	jujutesting "github.com/juju/juju/juju/testing"
    18  	"github.com/juju/juju/provider/ec2"
    19  	"github.com/juju/juju/state"
    20  	"github.com/juju/juju/storage/poolmanager"
    21  	"github.com/juju/juju/storage/provider"
    22  	"github.com/juju/juju/storage/provider/registry"
    23  	"github.com/juju/juju/testing"
    24  )
    25  
    26  const (
    27  	testPool           = "block"
    28  	testPersistentPool = "block-persistent"
    29  )
    30  
    31  func setupTestStorageSupport(c *gc.C, s *state.State) {
    32  	stsetts := state.NewStateSettings(s)
    33  	poolManager := poolmanager.New(stsetts)
    34  	_, err := poolManager.Create(testPool, provider.LoopProviderType, map[string]interface{}{"it": "works"})
    35  	c.Assert(err, jc.ErrorIsNil)
    36  	_, err = poolManager.Create(testPersistentPool, ec2.EBS_ProviderType, map[string]interface{}{"persistent": true})
    37  	c.Assert(err, jc.ErrorIsNil)
    38  
    39  	registry.RegisterEnvironStorageProviders("dummy", ec2.EBS_ProviderType)
    40  	registry.RegisterEnvironStorageProviders("dummyenv", ec2.EBS_ProviderType)
    41  }
    42  
    43  func makeStorageCons(pool string, size, count uint64) state.StorageConstraints {
    44  	return state.StorageConstraints{Pool: pool, Size: size, Count: count}
    45  }
    46  
    47  func createUnitWithStorage(c *gc.C, s *jujutesting.JujuConnSuite, poolName string) string {
    48  	ch := s.AddTestingCharm(c, "storage-block")
    49  	storage := map[string]state.StorageConstraints{
    50  		"data": makeStorageCons(poolName, 1024, 1),
    51  	}
    52  	service := s.AddTestingServiceWithStorage(c, "storage-block", ch, storage)
    53  	unit, err := service.AddUnit()
    54  	c.Assert(err, jc.ErrorIsNil)
    55  	err = s.State.AssignUnit(unit, state.AssignCleanEmpty)
    56  	c.Assert(err, jc.ErrorIsNil)
    57  
    58  	return unit.Tag().Id()
    59  }
    60  
    61  type cmdStorageSuite struct {
    62  	jujutesting.RepoSuite
    63  }
    64  
    65  func (s *cmdStorageSuite) SetUpTest(c *gc.C) {
    66  	s.RepoSuite.SetUpTest(c)
    67  	setupTestStorageSupport(c, s.State)
    68  }
    69  
    70  func runShow(c *gc.C, args ...string) *cmd.Context {
    71  	context, err := testing.RunCommand(c, envcmd.Wrap(&cmdstorage.ShowCommand{}), args...)
    72  	c.Assert(err, jc.ErrorIsNil)
    73  	return context
    74  }
    75  
    76  func (s *cmdStorageSuite) TestStorageShowEmpty(c *gc.C) {
    77  	_, err := testing.RunCommand(c, envcmd.Wrap(&cmdstorage.ShowCommand{}))
    78  	c.Assert(errors.Cause(err), gc.ErrorMatches, ".*must specify storage id.*")
    79  }
    80  
    81  func (s *cmdStorageSuite) TestStorageShowInvalidId(c *gc.C) {
    82  	_, err := testing.RunCommand(c, envcmd.Wrap(&cmdstorage.ShowCommand{}), "fluff")
    83  	c.Assert(errors.Cause(err), gc.ErrorMatches, ".*invalid storage id.*")
    84  }
    85  
    86  func (s *cmdStorageSuite) TestStorageShow(c *gc.C) {
    87  	createUnitWithStorage(c, &s.JujuConnSuite, testPool)
    88  
    89  	context := runShow(c, "data/0")
    90  	expected := `
    91  storage-block/0:
    92    data/0:
    93      storage: data
    94      kind: block
    95      status: pending
    96      persistent: false
    97  `[1:]
    98  	c.Assert(testing.Stdout(context), gc.Equals, expected)
    99  }
   100  
   101  func (s *cmdStorageSuite) TestStorageShowOneMatchingFilter(c *gc.C) {
   102  	createUnitWithStorage(c, &s.JujuConnSuite, testPool)
   103  
   104  	context := runShow(c, "data/0", "fluff/0")
   105  	expected := `
   106  storage-block/0:
   107    data/0:
   108      storage: data
   109      kind: block
   110      status: pending
   111      persistent: false
   112  `[1:]
   113  	c.Assert(testing.Stdout(context), gc.Equals, expected)
   114  }
   115  
   116  func (s *cmdStorageSuite) TestStorageShowNoMatch(c *gc.C) {
   117  	createUnitWithStorage(c, &s.JujuConnSuite, testPool)
   118  	context := runShow(c, "fluff/0")
   119  	c.Assert(testing.Stdout(context), gc.Equals, "{}\n")
   120  }
   121  
   122  func runList(c *gc.C) *cmd.Context {
   123  	context, err := testing.RunCommand(c, envcmd.Wrap(&cmdstorage.ListCommand{}))
   124  	c.Assert(err, jc.ErrorIsNil)
   125  	return context
   126  }
   127  
   128  func (s *cmdStorageSuite) TestStorageListEmpty(c *gc.C) {
   129  	context := runList(c)
   130  	c.Assert(testing.Stdout(context), gc.Equals, "")
   131  }
   132  
   133  func (s *cmdStorageSuite) TestStorageList(c *gc.C) {
   134  	createUnitWithStorage(c, &s.JujuConnSuite, testPool)
   135  
   136  	context := runList(c)
   137  	expected := `
   138  [Storage]       
   139  UNIT            ID     LOCATION STATUS  PERSISTENT 
   140  storage-block/0 data/0          pending false      
   141  
   142  `[1:]
   143  	c.Assert(testing.Stdout(context), gc.Equals, expected)
   144  }
   145  
   146  func (s *cmdStorageSuite) TestStorageListPersistent(c *gc.C) {
   147  	createUnitWithStorage(c, &s.JujuConnSuite, testPersistentPool)
   148  
   149  	context := runList(c)
   150  	expected := `
   151  [Storage]       
   152  UNIT            ID     LOCATION STATUS  PERSISTENT 
   153  storage-block/0 data/0          pending true       
   154  
   155  `[1:]
   156  	c.Assert(testing.Stdout(context), gc.Equals, expected)
   157  }
   158  
   159  func (s *cmdStorageSuite) TestStoragePersistentProvisioned(c *gc.C) {
   160  	createUnitWithStorage(c, &s.JujuConnSuite, testPool)
   161  	vol, err := s.State.StorageInstanceVolume(names.NewStorageTag("data/0"))
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	err = s.State.SetVolumeInfo(vol.VolumeTag(), state.VolumeInfo{
   164  		Size:       1024,
   165  		Persistent: true,
   166  		VolumeId:   "vol-ume",
   167  	})
   168  	c.Assert(err, jc.ErrorIsNil)
   169  
   170  	context := runShow(c, "data/0")
   171  	expected := `
   172  storage-block/0:
   173    data/0:
   174      storage: data
   175      kind: block
   176      status: pending
   177      persistent: true
   178  `[1:]
   179  	c.Assert(testing.Stdout(context), gc.Equals, expected)
   180  }
   181  
   182  func (s *cmdStorageSuite) TestStoragePersistentUnprovisioned(c *gc.C) {
   183  	createUnitWithStorage(c, &s.JujuConnSuite, testPersistentPool)
   184  
   185  	context := runShow(c, "data/0")
   186  	expected := `
   187  storage-block/0:
   188    data/0:
   189      storage: data
   190      kind: block
   191      status: pending
   192      persistent: true
   193  `[1:]
   194  	c.Assert(testing.Stdout(context), gc.Equals, expected)
   195  }
   196  
   197  func runPoolList(c *gc.C, args ...string) *cmd.Context {
   198  	context, err := testing.RunCommand(c, envcmd.Wrap(&cmdstorage.PoolListCommand{}), args...)
   199  	c.Assert(err, jc.ErrorIsNil)
   200  	return context
   201  }
   202  
   203  func (s *cmdStorageSuite) TestListPools(c *gc.C) {
   204  	context := runPoolList(c)
   205  	expected := `
   206  block:
   207    provider: loop
   208    attrs:
   209      it: works
   210  block-persistent:
   211    provider: ebs
   212    attrs:
   213      persistent: true
   214  ebs:
   215    provider: ebs
   216  loop:
   217    provider: loop
   218  rootfs:
   219    provider: rootfs
   220  tmpfs:
   221    provider: tmpfs
   222  `[1:]
   223  	c.Assert(testing.Stdout(context), gc.Equals, expected)
   224  }
   225  
   226  func (s *cmdStorageSuite) TestListPoolsTabular(c *gc.C) {
   227  	context := runPoolList(c, "--format", "tabular")
   228  	expected := `
   229  NAME              PROVIDER  ATTRS
   230  block             loop      it=works
   231  block-persistent  ebs       persistent=true
   232  ebs               ebs       
   233  loop              loop      
   234  rootfs            rootfs    
   235  tmpfs             tmpfs     
   236  
   237  `[1:]
   238  	c.Assert(testing.Stdout(context), gc.Equals, expected)
   239  }
   240  
   241  func (s *cmdStorageSuite) TestListPoolsName(c *gc.C) {
   242  	context := runPoolList(c, "--name", "block")
   243  	expected := `
   244  block:
   245    provider: loop
   246    attrs:
   247      it: works
   248  `[1:]
   249  	c.Assert(testing.Stdout(context), gc.Equals, expected)
   250  }
   251  
   252  func (s *cmdStorageSuite) TestListPoolsNameNoMatch(c *gc.C) {
   253  	context := runPoolList(c, "--name", "cranky")
   254  	c.Assert(testing.Stdout(context), gc.Equals, "")
   255  }
   256  
   257  func (s *cmdStorageSuite) TestListPoolsNameInvalid(c *gc.C) {
   258  	_, err := testing.RunCommand(c, envcmd.Wrap(&cmdstorage.PoolListCommand{}), "--name", "9oops")
   259  	c.Assert(errors.Cause(err), gc.ErrorMatches, ".*not valid.*")
   260  }
   261  
   262  func (s *cmdStorageSuite) TestListPoolsProvider(c *gc.C) {
   263  	context := runPoolList(c, "--provider", "ebs")
   264  	expected := `
   265  block-persistent:
   266    provider: ebs
   267    attrs:
   268      persistent: true
   269  ebs:
   270    provider: ebs
   271  `[1:]
   272  	c.Assert(testing.Stdout(context), gc.Equals, expected)
   273  }
   274  
   275  func (s *cmdStorageSuite) registerTmpProviderType(c *gc.C) {
   276  	cfg, err := s.State.EnvironConfig()
   277  	c.Assert(err, jc.ErrorIsNil)
   278  	registry.RegisterEnvironStorageProviders(cfg.Name(), provider.TmpfsProviderType)
   279  }
   280  
   281  func (s *cmdStorageSuite) TestListPoolsProviderNoMatch(c *gc.C) {
   282  	s.registerTmpProviderType(c)
   283  	context := runPoolList(c, "--provider", string(provider.TmpfsProviderType))
   284  	expected := `
   285  tmpfs:
   286    provider: tmpfs
   287  `[1:]
   288  	c.Assert(testing.Stdout(context), gc.Equals, expected)
   289  }
   290  
   291  func (s *cmdStorageSuite) TestListPoolsProviderUnregistered(c *gc.C) {
   292  	_, err := testing.RunCommand(c, envcmd.Wrap(&cmdstorage.PoolListCommand{}), "--provider", "oops")
   293  	c.Assert(errors.Cause(err), gc.ErrorMatches, ".*not supported.*")
   294  }
   295  
   296  func (s *cmdStorageSuite) TestListPoolsNameAndProvider(c *gc.C) {
   297  	context := runPoolList(c, "--name", "block", "--provider", "loop")
   298  	expected := `
   299  block:
   300    provider: loop
   301    attrs:
   302      it: works
   303  `[1:]
   304  	c.Assert(testing.Stdout(context), gc.Equals, expected)
   305  }
   306  
   307  func (s *cmdStorageSuite) TestListPoolsProviderAndNotName(c *gc.C) {
   308  	context := runPoolList(c, "--name", "fluff", "--provider", "ebs")
   309  	// there is no pool that matches this name AND type
   310  	c.Assert(testing.Stdout(context), gc.Equals, "")
   311  }
   312  
   313  func (s *cmdStorageSuite) TestListPoolsNameAndNotProvider(c *gc.C) {
   314  	s.registerTmpProviderType(c)
   315  	context := runPoolList(c, "--name", "block", "--provider", string(provider.TmpfsProviderType))
   316  	// no pool matches this name and this provider
   317  	c.Assert(testing.Stdout(context), gc.Equals, "")
   318  }
   319  
   320  func (s *cmdStorageSuite) TestListPoolsNotNameAndNotProvider(c *gc.C) {
   321  	s.registerTmpProviderType(c)
   322  	context := runPoolList(c, "--name", "fluff", "--provider", string(provider.TmpfsProviderType))
   323  	c.Assert(testing.Stdout(context), gc.Equals, "")
   324  }
   325  
   326  func runPoolCreate(c *gc.C, args ...string) *cmd.Context {
   327  	context, err := testing.RunCommand(c, envcmd.Wrap(&cmdstorage.PoolCreateCommand{}), args...)
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	return context
   330  }
   331  
   332  func (s *cmdStorageSuite) TestCreatePool(c *gc.C) {
   333  	pname := "ftPool"
   334  	context := runPoolCreate(c, pname, "loop", "smth=one")
   335  	c.Assert(testing.Stdout(context), gc.Equals, "")
   336  	assertPoolExists(c, s.State, pname, "loop", "smth=one")
   337  }
   338  
   339  func (s *cmdStorageSuite) assertCreatePoolError(c *gc.C, expected string, args ...string) {
   340  	_, err := testing.RunCommand(c, envcmd.Wrap(&cmdstorage.PoolCreateCommand{}), args...)
   341  	c.Assert(errors.Cause(err), gc.ErrorMatches, expected)
   342  }
   343  
   344  func (s *cmdStorageSuite) TestCreatePoolErrorNoAttrs(c *gc.C) {
   345  	s.assertCreatePoolError(c, ".*pool creation requires names, provider type and attrs for configuration.*", "loop", "ftPool")
   346  }
   347  
   348  func (s *cmdStorageSuite) TestCreatePoolErrorNoProvider(c *gc.C) {
   349  	s.assertCreatePoolError(c, ".*pool creation requires names, provider type and attrs for configuration.*", "oops provider", "smth=one")
   350  }
   351  
   352  func (s *cmdStorageSuite) TestCreatePoolErrorProviderType(c *gc.C) {
   353  	s.assertCreatePoolError(c, ".*not found.*", "loop", "ftPool", "smth=one")
   354  }
   355  
   356  func (s *cmdStorageSuite) TestCreatePoolDuplicateName(c *gc.C) {
   357  	pname := "ftPool"
   358  	context := runPoolCreate(c, pname, "loop", "smth=one")
   359  	c.Assert(testing.Stdout(context), gc.Equals, "")
   360  	assertPoolExists(c, s.State, pname, "loop", "smth=one")
   361  	s.assertCreatePoolError(c, ".*cannot overwrite existing settings*", pname, "loop", "smth=one")
   362  }
   363  
   364  func assertPoolExists(c *gc.C, st *state.State, pname, provider, attr string) {
   365  	stsetts := state.NewStateSettings(st)
   366  	poolManager := poolmanager.New(stsetts)
   367  
   368  	found, err := poolManager.List()
   369  	c.Assert(err, jc.ErrorIsNil)
   370  	c.Assert(len(found) > 0, jc.IsTrue)
   371  
   372  	exists := false
   373  	for _, one := range found {
   374  		if one.Name() == pname {
   375  			exists = true
   376  			c.Assert(string(one.Provider()), gc.Equals, provider)
   377  			// At this stage, only 1 attr is expected and checked
   378  			expectedAttrs := strings.Split(attr, "=")
   379  			value, ok := one.Attrs()[expectedAttrs[0]]
   380  			c.Assert(ok, jc.IsTrue)
   381  			c.Assert(value, gc.Equals, expectedAttrs[1])
   382  		}
   383  	}
   384  	c.Assert(exists, jc.IsTrue)
   385  }
   386  
   387  func runVolumeList(c *gc.C, args ...string) *cmd.Context {
   388  	context, err := testing.RunCommand(c,
   389  		envcmd.Wrap(&cmdstorage.VolumeListCommand{}), args...)
   390  	c.Assert(err, jc.ErrorIsNil)
   391  	return context
   392  }
   393  
   394  func (s *cmdStorageSuite) TestListVolumeInvalidMachine(c *gc.C) {
   395  	context := runVolumeList(c, "abc", "--format", "yaml")
   396  	c.Assert(testing.Stdout(context), gc.Equals, "")
   397  	c.Assert(testing.Stderr(context),
   398  		gc.Matches,
   399  		`parsing machine tag machine-abc: "machine-abc" is not a valid machine tag
   400  `)
   401  }
   402  
   403  func (s *cmdStorageSuite) TestListVolumeTabularFilterMatch(c *gc.C) {
   404  	createUnitWithStorage(c, &s.JujuConnSuite, testPersistentPool)
   405  	context := runVolumeList(c, "0")
   406  	expected := `
   407  MACHINE  UNIT             STORAGE  DEVICE  VOLUME  ID  SIZE  STATE    MESSAGE
   408  0        storage-block/0  data/0           0                 pending  
   409  
   410  `[1:]
   411  	c.Assert(testing.Stdout(context), gc.Equals, expected)
   412  	c.Assert(testing.Stderr(context), gc.Equals, "")
   413  }
   414  
   415  func runAddToUnit(c *gc.C, args ...string) *cmd.Context {
   416  	context, err := testing.RunCommand(c, envcmd.Wrap(&cmdstorage.AddCommand{}), args...)
   417  	c.Assert(err, jc.ErrorIsNil)
   418  	return context
   419  }
   420  
   421  func (s *cmdStorageSuite) TestStorageAddToUnitSuccess(c *gc.C) {
   422  	u := createUnitWithStorage(c, &s.JujuConnSuite, testPool)
   423  	instancesBefore, err := s.State.AllStorageInstances()
   424  	c.Assert(err, jc.ErrorIsNil)
   425  	volumesBefore, err := s.State.AllVolumes()
   426  	c.Assert(err, jc.ErrorIsNil)
   427  	s.assertStorageExist(c, instancesBefore, "data")
   428  
   429  	context := runAddToUnit(c, u, "allecto=1")
   430  	c.Assert(testing.Stdout(context), gc.Equals, "")
   431  	c.Assert(testing.Stderr(context), gc.Equals, "")
   432  
   433  	instancesAfter, err := s.State.AllStorageInstances()
   434  	c.Assert(err, jc.ErrorIsNil)
   435  	c.Assert(len(instancesAfter)-len(instancesBefore), gc.Equals, 1)
   436  	volumesAfter, err := s.State.AllVolumes()
   437  	c.Assert(err, jc.ErrorIsNil)
   438  	c.Assert(len(volumesAfter)-len(volumesBefore), gc.Equals, 1)
   439  	s.assertStorageExist(c, instancesAfter, "data", "allecto")
   440  }
   441  
   442  func (s *cmdStorageSuite) assertStorageExist(c *gc.C,
   443  	all []state.StorageInstance,
   444  	expected ...string) {
   445  
   446  	names := make([]string, len(all))
   447  	for i, one := range all {
   448  		names[i] = one.StorageName()
   449  	}
   450  	c.Assert(names, jc.SameContents, expected)
   451  }
   452  
   453  func (s *cmdStorageSuite) TestStorageAddToUnitFailure(c *gc.C) {
   454  	context := runAddToUnit(c, "fluffyunit/0", "allecto=1")
   455  	c.Assert(testing.Stdout(context), gc.Equals, "")
   456  	c.Assert(testing.Stderr(context), gc.Equals, "fail: storage \"allecto\": permission denied\n")
   457  }
   458  
   459  func (s *cmdStorageSuite) TestStorageAddToUnitHasVolumes(c *gc.C) {
   460  	// Reproducing Bug1462146
   461  	u := createUnitWithFileSystemStorage(c, &s.JujuConnSuite, "ebs")
   462  	instancesBefore, err := s.State.AllStorageInstances()
   463  	c.Assert(err, jc.ErrorIsNil)
   464  	s.assertStorageExist(c, instancesBefore, "data")
   465  	volumesBefore, err := s.State.AllVolumes()
   466  	c.Assert(err, jc.ErrorIsNil)
   467  	c.Assert(volumesBefore, gc.HasLen, 1)
   468  
   469  	context := runList(c)
   470  	c.Assert(testing.Stdout(context), gc.Equals, `
   471  [Storage]            
   472  UNIT                 ID     LOCATION STATUS  PERSISTENT 
   473  storage-filesystem/0 data/0          pending false      
   474  
   475  `[1:])
   476  	c.Assert(testing.Stderr(context), gc.Equals, "")
   477  
   478  	context = runAddToUnit(c, u, "data=ebs,1G")
   479  	c.Assert(testing.Stdout(context), gc.Equals, "")
   480  	c.Assert(testing.Stderr(context), gc.Equals, "")
   481  
   482  	instancesAfter, err := s.State.AllStorageInstances()
   483  	c.Assert(err, jc.ErrorIsNil)
   484  	c.Assert(len(instancesAfter)-len(instancesBefore), gc.Equals, 1)
   485  	s.assertStorageExist(c, instancesAfter, "data", "data")
   486  	volumesAfter, err := s.State.AllVolumes()
   487  	c.Assert(err, jc.ErrorIsNil)
   488  	c.Assert(volumesAfter, gc.HasLen, 2)
   489  
   490  	context = runList(c)
   491  	c.Assert(testing.Stdout(context), gc.Equals, `
   492  [Storage]            
   493  UNIT                 ID     LOCATION STATUS  PERSISTENT 
   494  storage-filesystem/0 data/0          pending false      
   495  storage-filesystem/0 data/1          pending false      
   496  
   497  `[1:])
   498  	c.Assert(testing.Stderr(context), gc.Equals, "")
   499  }
   500  
   501  func createUnitWithFileSystemStorage(c *gc.C, s *jujutesting.JujuConnSuite, poolName string) string {
   502  	ch := s.AddTestingCharm(c, "storage-filesystem")
   503  	storage := map[string]state.StorageConstraints{
   504  		"data": makeStorageCons(poolName, 1024, 1),
   505  	}
   506  	service := s.AddTestingServiceWithStorage(c, "storage-filesystem", ch, storage)
   507  	unit, err := service.AddUnit()
   508  	c.Assert(err, jc.ErrorIsNil)
   509  	err = s.State.AssignUnit(unit, state.AssignCleanEmpty)
   510  	c.Assert(err, jc.ErrorIsNil)
   511  
   512  	return unit.Tag().Id()
   513  }