github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/storage/attachments_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package storage_test
     5  
     6  import (
     7  	"io/ioutil"
     8  	"path/filepath"
     9  
    10  	"github.com/juju/errors"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	"gopkg.in/juju/charm.v6/hooks"
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/apiserver/params"
    17  	"github.com/juju/juju/core/model"
    18  	corestorage "github.com/juju/juju/storage"
    19  	"github.com/juju/juju/testing"
    20  	"github.com/juju/juju/worker/uniter/hook"
    21  	"github.com/juju/juju/worker/uniter/operation"
    22  	"github.com/juju/juju/worker/uniter/remotestate"
    23  	"github.com/juju/juju/worker/uniter/resolver"
    24  	"github.com/juju/juju/worker/uniter/storage"
    25  )
    26  
    27  type attachmentsSuite struct {
    28  	testing.BaseSuite
    29  
    30  	modelType model.ModelType
    31  }
    32  
    33  type caasAttachmentsSuite struct {
    34  	attachmentsSuite
    35  }
    36  
    37  type iaasAttachmentsSuite struct {
    38  	attachmentsSuite
    39  }
    40  
    41  var _ = gc.Suite(&caasAttachmentsSuite{})
    42  var _ = gc.Suite(&iaasAttachmentsSuite{})
    43  
    44  func (s *caasAttachmentsSuite) SetUpTest(c *gc.C) {
    45  	s.modelType = model.CAAS
    46  	s.attachmentsSuite.SetUpTest(c)
    47  }
    48  
    49  func (s *iaasAttachmentsSuite) SetUpTest(c *gc.C) {
    50  	s.modelType = model.IAAS
    51  	s.attachmentsSuite.SetUpTest(c)
    52  }
    53  
    54  func assertStorageTags(c *gc.C, a *storage.Attachments, tags ...names.StorageTag) {
    55  	sTags, err := a.StorageTags()
    56  	c.Assert(err, jc.ErrorIsNil)
    57  	c.Assert(sTags, jc.SameContents, tags)
    58  }
    59  
    60  func (s *attachmentsSuite) TestNewAttachments(c *gc.C) {
    61  	stateDir := filepath.Join(c.MkDir(), "nonexistent")
    62  	unitTag := names.NewUnitTag("mysql/0")
    63  	abort := make(chan struct{})
    64  	st := &mockStorageAccessor{
    65  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
    66  			c.Assert(u, gc.Equals, unitTag)
    67  			return nil, nil
    68  		},
    69  	}
    70  
    71  	_, err := storage.NewAttachments(st, unitTag, stateDir, abort)
    72  	c.Assert(err, jc.ErrorIsNil)
    73  	// state dir should have been created.
    74  	c.Assert(stateDir, jc.IsDirectory)
    75  }
    76  
    77  func (s *attachmentsSuite) TestNewAttachmentsInit(c *gc.C) {
    78  	stateDir := c.MkDir()
    79  	unitTag := names.NewUnitTag("mysql/0")
    80  	abort := make(chan struct{})
    81  
    82  	// Simulate remote state returning a single Alive storage attachment.
    83  	storageTag := names.NewStorageTag("data/0")
    84  	attachmentIds := []params.StorageAttachmentId{{
    85  		StorageTag: storageTag.String(),
    86  		UnitTag:    unitTag.String(),
    87  	}}
    88  	attachment := params.StorageAttachment{
    89  		StorageTag: storageTag.String(),
    90  		UnitTag:    unitTag.String(),
    91  		Life:       params.Alive,
    92  		Kind:       params.StorageKindBlock,
    93  		Location:   "/dev/sdb",
    94  	}
    95  
    96  	st := &mockStorageAccessor{
    97  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
    98  			c.Assert(u, gc.Equals, unitTag)
    99  			return attachmentIds, nil
   100  		},
   101  		storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) {
   102  			c.Assert(s, gc.Equals, storageTag)
   103  			return attachment, nil
   104  		},
   105  	}
   106  
   107  	withAttachments := func(f func(*storage.Attachments)) {
   108  		att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
   109  		c.Assert(err, jc.ErrorIsNil)
   110  		f(att)
   111  	}
   112  
   113  	// No state files, so no storagers will be started.
   114  	var called int
   115  	withAttachments(func(att *storage.Attachments) {
   116  		called++
   117  		c.Assert(att.Pending(), gc.Equals, 1)
   118  		err := att.ValidateHook(hook.Info{
   119  			Kind:      hooks.StorageAttached,
   120  			StorageId: storageTag.Id(),
   121  		})
   122  		c.Assert(err, gc.ErrorMatches, `unknown storage "data/0"`)
   123  		assertStorageTags(c, att) // no active attachment
   124  	})
   125  	c.Assert(called, gc.Equals, 1)
   126  
   127  	// Commit a storage-attached to local state and try again.
   128  	state0, err := storage.ReadStateFile(stateDir, storageTag)
   129  	c.Assert(err, jc.ErrorIsNil)
   130  	err = state0.CommitHook(hook.Info{Kind: hooks.StorageAttached, StorageId: "data/0"})
   131  	c.Assert(err, jc.ErrorIsNil)
   132  	// Create an extra one so we can make sure it gets removed.
   133  	state1, err := storage.ReadStateFile(stateDir, names.NewStorageTag("data/1"))
   134  	c.Assert(err, jc.ErrorIsNil)
   135  	err = state1.CommitHook(hook.Info{Kind: hooks.StorageAttached, StorageId: "data/1"})
   136  	c.Assert(err, jc.ErrorIsNil)
   137  
   138  	withAttachments(func(att *storage.Attachments) {
   139  		// We should be able to get the initial storage context
   140  		// for existing storage immediately, without having to
   141  		// wait for any hooks to fire.
   142  		ctx, err := att.Storage(storageTag)
   143  		c.Assert(err, jc.ErrorIsNil)
   144  		c.Assert(ctx, gc.NotNil)
   145  		c.Assert(ctx.Tag(), gc.Equals, storageTag)
   146  		c.Assert(ctx.Tag(), gc.Equals, storageTag)
   147  		c.Assert(ctx.Kind(), gc.Equals, corestorage.StorageKindBlock)
   148  		c.Assert(ctx.Location(), gc.Equals, "/dev/sdb")
   149  
   150  		called++
   151  		c.Assert(att.Pending(), gc.Equals, 0)
   152  		err = att.ValidateHook(hook.Info{
   153  			Kind:      hooks.StorageDetaching,
   154  			StorageId: storageTag.Id(),
   155  		})
   156  		c.Assert(err, jc.ErrorIsNil)
   157  		err = att.ValidateHook(hook.Info{
   158  			Kind:      hooks.StorageAttached,
   159  			StorageId: "data/1",
   160  		})
   161  		c.Assert(err, gc.ErrorMatches, `unknown storage "data/1"`)
   162  		assertStorageTags(c, att, storageTag)
   163  	})
   164  	c.Assert(called, gc.Equals, 2)
   165  	c.Assert(filepath.Join(stateDir, "data-0"), jc.IsNonEmptyFile)
   166  	c.Assert(filepath.Join(stateDir, "data-1"), jc.DoesNotExist)
   167  }
   168  
   169  func (s *attachmentsSuite) TestAttachmentsUpdateShortCircuitDeath(c *gc.C) {
   170  	stateDir := c.MkDir()
   171  	unitTag := names.NewUnitTag("mysql/0")
   172  	abort := make(chan struct{})
   173  
   174  	storageTag0 := names.NewStorageTag("data/0")
   175  	storageTag1 := names.NewStorageTag("data/1")
   176  
   177  	removed := names.NewSet()
   178  	st := &mockStorageAccessor{
   179  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   180  			return nil, nil
   181  		},
   182  		remove: func(s names.StorageTag, u names.UnitTag) error {
   183  			c.Assert(u, gc.Equals, unitTag)
   184  			removed.Add(s)
   185  			return nil
   186  		},
   187  	}
   188  
   189  	att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
   190  	c.Assert(err, jc.ErrorIsNil)
   191  	r := storage.NewResolver(att, s.modelType)
   192  
   193  	// First make sure we create a storage-attached hook operation for
   194  	// data/0. We do this to show that until the hook is *committed*,
   195  	// we will still short-circuit removal.
   196  	localState := resolver.LocalState{State: operation.State{
   197  		Kind: operation.Continue,
   198  	}}
   199  	_, err = r.NextOp(localState, remotestate.Snapshot{
   200  		Life: params.Alive,
   201  		Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   202  			storageTag0: {
   203  				Life:     params.Alive,
   204  				Kind:     params.StorageKindBlock,
   205  				Location: "/dev/sdb",
   206  				Attached: true,
   207  			},
   208  		},
   209  	}, &mockOperations{})
   210  	c.Assert(err, jc.ErrorIsNil)
   211  
   212  	for _, storageTag := range []names.StorageTag{storageTag0, storageTag1} {
   213  		_, err = r.NextOp(localState, remotestate.Snapshot{
   214  			Life: params.Alive,
   215  			Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   216  				storageTag: {Life: params.Dying},
   217  			},
   218  		}, nil)
   219  		c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   220  	}
   221  	c.Assert(removed.SortedValues(), jc.DeepEquals, []names.Tag{
   222  		storageTag0, storageTag1,
   223  	})
   224  }
   225  
   226  func (s *attachmentsSuite) TestAttachmentsStorage(c *gc.C) {
   227  	stateDir := c.MkDir()
   228  	unitTag := names.NewUnitTag("mysql/0")
   229  	abort := make(chan struct{})
   230  
   231  	st := &mockStorageAccessor{
   232  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   233  			return nil, nil
   234  		},
   235  	}
   236  
   237  	att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
   238  	c.Assert(err, jc.ErrorIsNil)
   239  	r := storage.NewResolver(att, s.modelType)
   240  
   241  	storageTag := names.NewStorageTag("data/0")
   242  	_, err = att.Storage(storageTag)
   243  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   244  	assertStorageTags(c, att)
   245  
   246  	// Inform the resolver of an attachment.
   247  	localState := resolver.LocalState{State: operation.State{
   248  		Kind: operation.Continue,
   249  	}}
   250  	op, err := r.NextOp(localState, remotestate.Snapshot{
   251  		Life: params.Alive,
   252  		Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   253  			storageTag: {
   254  				Kind:     params.StorageKindBlock,
   255  				Life:     params.Alive,
   256  				Location: "/dev/sdb",
   257  				Attached: true,
   258  			},
   259  		},
   260  	}, &mockOperations{})
   261  	c.Assert(err, jc.ErrorIsNil)
   262  	c.Assert(op.String(), gc.Equals, "run hook storage-attached")
   263  	assertStorageTags(c, att, storageTag)
   264  
   265  	ctx, err := att.Storage(storageTag)
   266  	c.Assert(err, jc.ErrorIsNil)
   267  	c.Assert(ctx, gc.NotNil)
   268  	c.Assert(ctx.Tag(), gc.Equals, storageTag)
   269  	c.Assert(ctx.Kind(), gc.Equals, corestorage.StorageKindBlock)
   270  	c.Assert(ctx.Location(), gc.Equals, "/dev/sdb")
   271  }
   272  
   273  func (s *attachmentsSuite) TestAttachmentsCommitHook(c *gc.C) {
   274  	stateDir := c.MkDir()
   275  	unitTag := names.NewUnitTag("mysql/0")
   276  	abort := make(chan struct{})
   277  
   278  	var removed bool
   279  	storageTag := names.NewStorageTag("data/0")
   280  	st := &mockStorageAccessor{
   281  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   282  			return nil, nil
   283  		},
   284  		remove: func(s names.StorageTag, u names.UnitTag) error {
   285  			removed = true
   286  			c.Assert(s, gc.Equals, storageTag)
   287  			return nil
   288  		},
   289  	}
   290  
   291  	att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
   292  	c.Assert(err, jc.ErrorIsNil)
   293  	r := storage.NewResolver(att, s.modelType)
   294  
   295  	// Inform the resolver of an attachment.
   296  	localState := resolver.LocalState{State: operation.State{
   297  		Kind: operation.Continue,
   298  	}}
   299  	_, err = r.NextOp(localState, remotestate.Snapshot{
   300  		Life: params.Alive,
   301  		Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   302  			storageTag: {
   303  				Kind:     params.StorageKindBlock,
   304  				Life:     params.Alive,
   305  				Location: "/dev/sdb",
   306  				Attached: true,
   307  			},
   308  		},
   309  	}, &mockOperations{})
   310  	c.Assert(err, jc.ErrorIsNil)
   311  	c.Assert(att.Pending(), gc.Equals, 1)
   312  
   313  	// No file exists until storage-attached is committed.
   314  	stateFile := filepath.Join(stateDir, "data-0")
   315  	c.Assert(stateFile, jc.DoesNotExist)
   316  
   317  	err = att.CommitHook(hook.Info{
   318  		Kind:      hooks.StorageAttached,
   319  		StorageId: storageTag.Id(),
   320  	})
   321  	c.Assert(err, jc.ErrorIsNil)
   322  	data, err := ioutil.ReadFile(stateFile)
   323  	c.Assert(err, jc.ErrorIsNil)
   324  	c.Assert(string(data), gc.Equals, "attached: true\n")
   325  	c.Assert(att.Pending(), gc.Equals, 0)
   326  
   327  	c.Assert(removed, jc.IsFalse)
   328  	err = att.CommitHook(hook.Info{
   329  		Kind:      hooks.StorageDetaching,
   330  		StorageId: storageTag.Id(),
   331  	})
   332  	c.Assert(err, jc.ErrorIsNil)
   333  	c.Assert(stateFile, jc.DoesNotExist)
   334  	c.Assert(removed, jc.IsTrue)
   335  }
   336  
   337  func (s *attachmentsSuite) TestAttachmentsSetDying(c *gc.C) {
   338  	stateDir := c.MkDir()
   339  	unitTag := names.NewUnitTag("mysql/0")
   340  	abort := make(chan struct{})
   341  
   342  	storageTag := names.NewStorageTag("data/0")
   343  	var destroyed, removed bool
   344  	st := &mockStorageAccessor{
   345  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   346  			c.Assert(u, gc.Equals, unitTag)
   347  			return []params.StorageAttachmentId{{
   348  				StorageTag: storageTag.String(),
   349  				UnitTag:    unitTag.String(),
   350  			}}, nil
   351  		},
   352  		storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) {
   353  			c.Assert(u, gc.Equals, unitTag)
   354  			c.Assert(s, gc.Equals, storageTag)
   355  			return params.StorageAttachment{}, &params.Error{
   356  				Message: "not provisioned",
   357  				Code:    params.CodeNotProvisioned,
   358  			}
   359  		},
   360  		destroyUnitStorageAttachments: func(u names.UnitTag) error {
   361  			c.Assert(u, gc.Equals, unitTag)
   362  			destroyed = true
   363  			return nil
   364  		},
   365  		remove: func(s names.StorageTag, u names.UnitTag) error {
   366  			c.Assert(removed, jc.IsFalse)
   367  			c.Assert(s, gc.Equals, storageTag)
   368  			c.Assert(u, gc.Equals, unitTag)
   369  			removed = true
   370  			return nil
   371  		},
   372  	}
   373  
   374  	att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
   375  	c.Assert(err, jc.ErrorIsNil)
   376  	c.Assert(att.Pending(), gc.Equals, 1)
   377  	r := storage.NewResolver(att, s.modelType)
   378  
   379  	// Inform the resolver that the unit is Dying. The storage is still
   380  	// Alive, and is now provisioned, but will be destroyed and removed
   381  	// by the resolver.
   382  	localState := resolver.LocalState{State: operation.State{
   383  		Kind: operation.Continue,
   384  	}}
   385  	_, err = r.NextOp(localState, remotestate.Snapshot{
   386  		Life: params.Dying,
   387  		Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   388  			storageTag: {
   389  				Kind:     params.StorageKindBlock,
   390  				Life:     params.Alive,
   391  				Location: "/dev/sdb",
   392  				Attached: true,
   393  			},
   394  		},
   395  	}, &mockOperations{})
   396  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   397  	c.Assert(destroyed, jc.IsTrue)
   398  	c.Assert(att.Pending(), gc.Equals, 0)
   399  	c.Assert(removed, jc.IsTrue)
   400  }
   401  
   402  func (s *attachmentsSuite) TestAttachmentsWaitPending(c *gc.C) {
   403  	stateDir := c.MkDir()
   404  	unitTag := names.NewUnitTag("mysql/0")
   405  	abort := make(chan struct{})
   406  
   407  	storageTag := names.NewStorageTag("data/0")
   408  	st := &mockStorageAccessor{
   409  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   410  			return nil, nil
   411  		},
   412  	}
   413  
   414  	att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
   415  	c.Assert(err, jc.ErrorIsNil)
   416  	r := storage.NewResolver(att, s.modelType)
   417  
   418  	nextOp := func(installed bool) error {
   419  		localState := resolver.LocalState{State: operation.State{
   420  			Installed: installed,
   421  			Kind:      operation.Continue,
   422  		}}
   423  		_, err := r.NextOp(localState, remotestate.Snapshot{
   424  			Life: params.Alive,
   425  			Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   426  				storageTag: {
   427  					Life:     params.Alive,
   428  					Attached: false,
   429  				},
   430  			},
   431  		}, &mockOperations{})
   432  		return err
   433  	}
   434  
   435  	// Inform the resolver of a new, unprovisioned storage attachment.
   436  	// For IAAS models, before install, we should wait for its completion;
   437  	// after install, we should not.
   438  	err = nextOp(false /* workload not installed */)
   439  	c.Assert(att.Pending(), gc.Equals, 1)
   440  
   441  	if s.modelType == model.IAAS {
   442  		c.Assert(err, gc.Equals, resolver.ErrWaiting)
   443  	} else {
   444  		c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   445  	}
   446  
   447  	err = nextOp(true /* workload installed */)
   448  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   449  }