github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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  	"github.com/juju/names"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  	"gopkg.in/juju/charm.v6-unstable/hooks"
    15  
    16  	"github.com/juju/juju/apiserver/params"
    17  	corestorage "github.com/juju/juju/storage"
    18  	"github.com/juju/juju/testing"
    19  	"github.com/juju/juju/worker/uniter/hook"
    20  	"github.com/juju/juju/worker/uniter/operation"
    21  	"github.com/juju/juju/worker/uniter/remotestate"
    22  	"github.com/juju/juju/worker/uniter/resolver"
    23  	"github.com/juju/juju/worker/uniter/storage"
    24  )
    25  
    26  type attachmentsSuite struct {
    27  	testing.BaseSuite
    28  }
    29  
    30  var _ = gc.Suite(&attachmentsSuite{})
    31  
    32  func assertStorageTags(c *gc.C, a *storage.Attachments, tags ...names.StorageTag) {
    33  	sTags, err := a.StorageTags()
    34  	c.Assert(err, jc.ErrorIsNil)
    35  	c.Assert(sTags, jc.SameContents, tags)
    36  }
    37  
    38  func (s *attachmentsSuite) TestNewAttachments(c *gc.C) {
    39  	stateDir := filepath.Join(c.MkDir(), "nonexistent")
    40  	unitTag := names.NewUnitTag("mysql/0")
    41  	abort := make(chan struct{})
    42  	st := &mockStorageAccessor{
    43  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
    44  			c.Assert(u, gc.Equals, unitTag)
    45  			return nil, nil
    46  		},
    47  	}
    48  
    49  	_, err := storage.NewAttachments(st, unitTag, stateDir, abort)
    50  	c.Assert(err, jc.ErrorIsNil)
    51  	// state dir should have been created.
    52  	c.Assert(stateDir, jc.IsDirectory)
    53  }
    54  
    55  func (s *attachmentsSuite) TestNewAttachmentsInit(c *gc.C) {
    56  	stateDir := c.MkDir()
    57  	unitTag := names.NewUnitTag("mysql/0")
    58  	abort := make(chan struct{})
    59  
    60  	// Simulate remote state returning a single Alive storage attachment.
    61  	attachmentIds := []params.StorageAttachmentId{{
    62  		StorageTag: "storage-data-0",
    63  		UnitTag:    unitTag.String(),
    64  	}}
    65  	st := &mockStorageAccessor{
    66  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
    67  			c.Assert(u, gc.Equals, unitTag)
    68  			return attachmentIds, nil
    69  		},
    70  	}
    71  
    72  	storageTag := names.NewStorageTag("data/0")
    73  	withAttachments := func(f func(*storage.Attachments)) {
    74  		att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
    75  		c.Assert(err, jc.ErrorIsNil)
    76  		f(att)
    77  	}
    78  
    79  	// No state files, so no storagers will be started.
    80  	var called int
    81  	withAttachments(func(att *storage.Attachments) {
    82  		called++
    83  		c.Assert(att.Pending(), gc.Equals, 1)
    84  		err := att.ValidateHook(hook.Info{
    85  			Kind:      hooks.StorageAttached,
    86  			StorageId: storageTag.Id(),
    87  		})
    88  		c.Assert(err, gc.ErrorMatches, `unknown storage "data/0"`)
    89  		assertStorageTags(c, att) // no active attachment
    90  	})
    91  	c.Assert(called, gc.Equals, 1)
    92  
    93  	// Commit a storage-attached to local state and try again.
    94  	state0, err := storage.ReadStateFile(stateDir, storageTag)
    95  	c.Assert(err, jc.ErrorIsNil)
    96  	err = state0.CommitHook(hook.Info{Kind: hooks.StorageAttached, StorageId: "data/0"})
    97  	c.Assert(err, jc.ErrorIsNil)
    98  	// Create an extra one so we can make sure it gets removed.
    99  	state1, err := storage.ReadStateFile(stateDir, names.NewStorageTag("data/1"))
   100  	c.Assert(err, jc.ErrorIsNil)
   101  	err = state1.CommitHook(hook.Info{Kind: hooks.StorageAttached, StorageId: "data/1"})
   102  	c.Assert(err, jc.ErrorIsNil)
   103  
   104  	withAttachments(func(att *storage.Attachments) {
   105  		called++
   106  		c.Assert(att.Pending(), gc.Equals, 0)
   107  		err := att.ValidateHook(hook.Info{
   108  			Kind:      hooks.StorageDetaching,
   109  			StorageId: storageTag.Id(),
   110  		})
   111  		c.Assert(err, jc.ErrorIsNil)
   112  		err = att.ValidateHook(hook.Info{
   113  			Kind:      hooks.StorageAttached,
   114  			StorageId: "data/1",
   115  		})
   116  		c.Assert(err, gc.ErrorMatches, `unknown storage "data/1"`)
   117  		assertStorageTags(c, att, storageTag)
   118  	})
   119  	c.Assert(called, gc.Equals, 2)
   120  	c.Assert(filepath.Join(stateDir, "data-0"), jc.IsNonEmptyFile)
   121  	c.Assert(filepath.Join(stateDir, "data-1"), jc.DoesNotExist)
   122  }
   123  
   124  func (s *attachmentsSuite) TestAttachmentsUpdateShortCircuitDeath(c *gc.C) {
   125  	stateDir := c.MkDir()
   126  	unitTag := names.NewUnitTag("mysql/0")
   127  	abort := make(chan struct{})
   128  
   129  	var removed bool
   130  	storageTag := names.NewStorageTag("data/0")
   131  	st := &mockStorageAccessor{
   132  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   133  			c.Assert(u, gc.Equals, unitTag)
   134  			return nil, nil
   135  		},
   136  		storageAttachmentLife: func(ids []params.StorageAttachmentId) ([]params.LifeResult, error) {
   137  			return []params.LifeResult{{Life: params.Dying}}, nil
   138  		},
   139  		remove: func(s names.StorageTag, u names.UnitTag) error {
   140  			removed = true
   141  			c.Assert(s, gc.Equals, storageTag)
   142  			c.Assert(u, gc.Equals, unitTag)
   143  			return nil
   144  		},
   145  	}
   146  
   147  	att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
   148  	c.Assert(err, jc.ErrorIsNil)
   149  	err = att.UpdateStorage([]names.StorageTag{storageTag})
   150  	c.Assert(err, jc.ErrorIsNil)
   151  	c.Assert(removed, jc.IsTrue)
   152  }
   153  
   154  func (s *attachmentsSuite) TestAttachmentsStorage(c *gc.C) {
   155  	stateDir := c.MkDir()
   156  	unitTag := names.NewUnitTag("mysql/0")
   157  	abort := make(chan struct{})
   158  
   159  	storageTag := names.NewStorageTag("data/0")
   160  	attachment := params.StorageAttachment{
   161  		StorageTag: storageTag.String(),
   162  		UnitTag:    unitTag.String(),
   163  		Life:       params.Alive,
   164  		Kind:       params.StorageKindBlock,
   165  		Location:   "/dev/sdb",
   166  	}
   167  
   168  	st := &mockStorageAccessor{
   169  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   170  			c.Assert(u, gc.Equals, unitTag)
   171  			return nil, nil
   172  		},
   173  		storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) {
   174  			c.Assert(s, gc.Equals, storageTag)
   175  			return attachment, nil
   176  		},
   177  	}
   178  
   179  	att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
   180  	c.Assert(err, jc.ErrorIsNil)
   181  
   182  	// There should be no context for data/0 until a required remote state change occurs.
   183  	_, ok := att.Storage(storageTag)
   184  	c.Assert(ok, jc.Satisfies, errors.IsNotFound)
   185  	assertStorageTags(c, att)
   186  
   187  	err = att.UpdateStorage([]names.StorageTag{storageTag})
   188  	c.Assert(err, jc.ErrorIsNil)
   189  	assertStorageTags(c, att, storageTag)
   190  
   191  	storageResolver := storage.NewResolver(att)
   192  	storage.SetStorageLife(storageResolver, map[names.StorageTag]params.Life{
   193  		storageTag: params.Alive,
   194  	})
   195  	localState := resolver.LocalState{
   196  		State: operation.State{
   197  			Kind: operation.Continue,
   198  		},
   199  	}
   200  	remoteState := remotestate.Snapshot{
   201  		Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   202  			storageTag: remotestate.StorageSnapshot{
   203  				Kind:     params.StorageKindBlock,
   204  				Life:     params.Alive,
   205  				Location: "/dev/sdb",
   206  				Attached: true,
   207  			},
   208  		},
   209  	}
   210  	op, err := storageResolver.NextOp(localState, remoteState, &mockOperations{})
   211  	c.Assert(err, jc.ErrorIsNil)
   212  	c.Assert(op.String(), gc.Equals, "run hook storage-attached")
   213  
   214  	ctx, err := att.Storage(storageTag)
   215  	c.Assert(err, jc.ErrorIsNil)
   216  	c.Assert(ctx, gc.NotNil)
   217  	c.Assert(ctx.Tag(), gc.Equals, storageTag)
   218  	c.Assert(ctx.Kind(), gc.Equals, corestorage.StorageKindBlock)
   219  	c.Assert(ctx.Location(), gc.Equals, "/dev/sdb")
   220  }
   221  
   222  func (s *attachmentsSuite) TestAttachmentsCommitHook(c *gc.C) {
   223  	stateDir := c.MkDir()
   224  	unitTag := names.NewUnitTag("mysql/0")
   225  	abort := make(chan struct{})
   226  
   227  	var removed bool
   228  	storageTag := names.NewStorageTag("data/0")
   229  	attachment := params.StorageAttachment{
   230  		StorageTag: storageTag.String(),
   231  		UnitTag:    unitTag.String(),
   232  		Life:       params.Alive,
   233  		Kind:       params.StorageKindBlock,
   234  		Location:   "/dev/sdb",
   235  	}
   236  	st := &mockStorageAccessor{
   237  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   238  			c.Assert(u, gc.Equals, unitTag)
   239  			return nil, nil
   240  		},
   241  		storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) {
   242  			c.Assert(s, gc.Equals, storageTag)
   243  			return attachment, nil
   244  		},
   245  		remove: func(s names.StorageTag, u names.UnitTag) error {
   246  			removed = true
   247  			c.Assert(s, gc.Equals, storageTag)
   248  			return nil
   249  		},
   250  	}
   251  
   252  	att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	err = att.UpdateStorage([]names.StorageTag{storageTag})
   255  	c.Assert(err, jc.ErrorIsNil)
   256  	c.Assert(att.Pending(), gc.Equals, 1)
   257  
   258  	stateFile := filepath.Join(stateDir, "data-0")
   259  	c.Assert(stateFile, jc.DoesNotExist)
   260  
   261  	err = att.CommitHook(hook.Info{
   262  		Kind:      hooks.StorageAttached,
   263  		StorageId: storageTag.Id(),
   264  	})
   265  	c.Assert(err, jc.ErrorIsNil)
   266  	data, err := ioutil.ReadFile(stateFile)
   267  	c.Assert(err, jc.ErrorIsNil)
   268  	c.Assert(string(data), gc.Equals, "attached: true\n")
   269  	c.Assert(att.Pending(), gc.Equals, 0)
   270  
   271  	c.Assert(removed, jc.IsFalse)
   272  	err = att.CommitHook(hook.Info{
   273  		Kind:      hooks.StorageDetaching,
   274  		StorageId: storageTag.Id(),
   275  	})
   276  	c.Assert(err, jc.ErrorIsNil)
   277  	c.Assert(stateFile, jc.DoesNotExist)
   278  	c.Assert(removed, jc.IsTrue)
   279  }
   280  
   281  func (s *attachmentsSuite) TestAttachmentsSetDying(c *gc.C) {
   282  	stateDir := c.MkDir()
   283  	unitTag := names.NewUnitTag("mysql/0")
   284  	storageTag0 := names.NewStorageTag("data/0")
   285  	storageTag1 := names.NewStorageTag("data/1")
   286  	abort := make(chan struct{})
   287  
   288  	var destroyed, removed bool
   289  	st := &mockStorageAccessor{
   290  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   291  			c.Assert(u, gc.Equals, unitTag)
   292  			return []params.StorageAttachmentId{{
   293  				StorageTag: storageTag0.String(),
   294  				UnitTag:    unitTag.String(),
   295  			}, {
   296  				StorageTag: storageTag1.String(),
   297  				UnitTag:    unitTag.String(),
   298  			}}, nil
   299  		},
   300  		storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) {
   301  			c.Assert(u, gc.Equals, unitTag)
   302  			if s == storageTag0 {
   303  				return params.StorageAttachment{}, &params.Error{
   304  					Message: "not provisioned",
   305  					Code:    params.CodeNotProvisioned,
   306  				}
   307  			}
   308  			c.Assert(s, gc.Equals, storageTag1)
   309  			return params.StorageAttachment{
   310  				StorageTag: storageTag1.String(),
   311  				UnitTag:    unitTag.String(),
   312  				Life:       params.Dying,
   313  				Kind:       params.StorageKindBlock,
   314  				Location:   "/dev/sdb",
   315  			}, nil
   316  		},
   317  		storageAttachmentLife: func(ids []params.StorageAttachmentId) ([]params.LifeResult, error) {
   318  			results := make([]params.LifeResult, len(ids))
   319  			for i := range ids {
   320  				results[i].Life = params.Dying
   321  			}
   322  			return results, nil
   323  		},
   324  		destroyUnitStorageAttachments: func(u names.UnitTag) error {
   325  			c.Assert(u, gc.Equals, unitTag)
   326  			destroyed = true
   327  			return nil
   328  		},
   329  		remove: func(s names.StorageTag, u names.UnitTag) error {
   330  			c.Assert(removed, jc.IsFalse)
   331  			c.Assert(s, gc.Equals, storageTag0)
   332  			c.Assert(u, gc.Equals, unitTag)
   333  			removed = true
   334  			return nil
   335  		},
   336  	}
   337  
   338  	state1, err := storage.ReadStateFile(stateDir, storageTag1)
   339  	c.Assert(err, jc.ErrorIsNil)
   340  	err = state1.CommitHook(hook.Info{Kind: hooks.StorageAttached, StorageId: storageTag1.Id()})
   341  	c.Assert(err, jc.ErrorIsNil)
   342  
   343  	att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
   344  	c.Assert(err, jc.ErrorIsNil)
   345  	c.Assert(att.Pending(), gc.Equals, 1)
   346  
   347  	err = att.SetDying()
   348  	c.Assert(err, jc.ErrorIsNil)
   349  	c.Assert(att.Pending(), gc.Equals, 0)
   350  	c.Assert(destroyed, jc.IsTrue)
   351  	c.Assert(removed, jc.IsTrue)
   352  }
   353  
   354  type attachmentsUpdateSuite struct {
   355  	testing.BaseSuite
   356  	unitTag           names.UnitTag
   357  	storageTag0       names.StorageTag
   358  	storageTag1       names.StorageTag
   359  	attachmentsByTag  map[names.StorageTag]*params.StorageAttachment
   360  	unitAttachmentIds map[names.UnitTag][]params.StorageAttachmentId
   361  	att               *storage.Attachments
   362  }
   363  
   364  var _ = gc.Suite(&attachmentsUpdateSuite{})
   365  
   366  func (s *attachmentsUpdateSuite) SetUpTest(c *gc.C) {
   367  	s.BaseSuite.SetUpTest(c)
   368  	s.unitTag = names.NewUnitTag("mysql/0")
   369  	s.storageTag0 = names.NewStorageTag("data/0")
   370  	s.storageTag1 = names.NewStorageTag("data/1")
   371  	s.attachmentsByTag = map[names.StorageTag]*params.StorageAttachment{
   372  		s.storageTag0: {
   373  			StorageTag: s.storageTag0.String(),
   374  			UnitTag:    s.unitTag.String(),
   375  			Life:       params.Alive,
   376  			Kind:       params.StorageKindBlock,
   377  			Location:   "/dev/sdb",
   378  		},
   379  		s.storageTag1: {
   380  			StorageTag: s.storageTag1.String(),
   381  			UnitTag:    s.unitTag.String(),
   382  			Life:       params.Dying,
   383  			Kind:       params.StorageKindBlock,
   384  			Location:   "/dev/sdb",
   385  		},
   386  	}
   387  	s.unitAttachmentIds = nil
   388  
   389  	st := &mockStorageAccessor{
   390  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   391  			c.Assert(u, gc.Equals, s.unitTag)
   392  			return s.unitAttachmentIds[u], nil
   393  		},
   394  		storageAttachment: func(storageTag names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) {
   395  			att, ok := s.attachmentsByTag[storageTag]
   396  			c.Assert(ok, jc.IsTrue)
   397  			return *att, nil
   398  		},
   399  		remove: func(storageTag names.StorageTag, u names.UnitTag) error {
   400  			c.Assert(storageTag, gc.Equals, s.storageTag1)
   401  			return nil
   402  		},
   403  	}
   404  
   405  	stateDir := c.MkDir()
   406  	abort := make(chan struct{})
   407  	var err error
   408  	s.att, err = storage.NewAttachments(st, s.unitTag, stateDir, abort)
   409  	c.Assert(err, jc.ErrorIsNil)
   410  }
   411  
   412  func (s *attachmentsUpdateSuite) TestAttachmentsUpdateUntrackedAlive(c *gc.C) {
   413  	// data/0 is initially unattached and untracked, so
   414  	// updating with Alive will cause a storager to be
   415  	// started and a storage-attached event to be emitted.
   416  	assertStorageTags(c, s.att)
   417  	for i := 0; i < 2; i++ {
   418  		// Updating twice, to ensure idempotency.
   419  		err := s.att.UpdateStorage([]names.StorageTag{s.storageTag0})
   420  		c.Assert(err, jc.ErrorIsNil)
   421  	}
   422  	assertStorageTags(c, s.att, s.storageTag0)
   423  	c.Assert(s.att.Pending(), gc.Equals, 1)
   424  }
   425  
   426  func (s *attachmentsUpdateSuite) TestAttachmentsUpdateUntrackedDying(c *gc.C) {
   427  	// data/1 is initially unattached and untracked, so
   428  	// updating with Dying will not cause a storager to
   429  	// be started.
   430  	err := s.att.UpdateStorage([]names.StorageTag{s.storageTag1})
   431  	c.Assert(err, jc.ErrorIsNil)
   432  	c.Assert(s.att.Pending(), gc.Equals, 0)
   433  	assertStorageTags(c, s.att)
   434  }
   435  
   436  func (s *attachmentsUpdateSuite) TestAttachmentsRefresh(c *gc.C) {
   437  	// This test combines the above two.
   438  	s.unitAttachmentIds = map[names.UnitTag][]params.StorageAttachmentId{
   439  		s.unitTag: []params.StorageAttachmentId{{
   440  			StorageTag: s.storageTag0.String(),
   441  			UnitTag:    s.unitTag.String(),
   442  		}, {
   443  			StorageTag: s.storageTag1.String(),
   444  			UnitTag:    s.unitTag.String(),
   445  		}},
   446  	}
   447  	for i := 0; i < 2; i++ {
   448  		// Refresh twice, to ensure idempotency.
   449  		err := s.att.Refresh()
   450  		c.Assert(err, jc.ErrorIsNil)
   451  	}
   452  	c.Assert(s.att.Pending(), gc.Equals, 1)
   453  }
   454  
   455  func (s *attachmentsUpdateSuite) TestAttachmentsUpdateShortCircuitNoHooks(c *gc.C) {
   456  	// Cause an Alive hook to be queued, but don't consume it;
   457  	// then update to Dying, and ensure no hooks are generated.
   458  	// Additionally, the storager should be stopped and no
   459  	// longer tracked.
   460  	s.attachmentsByTag[s.storageTag1].Life = params.Alive
   461  	err := s.att.UpdateStorage([]names.StorageTag{s.storageTag1})
   462  	c.Assert(err, jc.ErrorIsNil)
   463  	err = s.att.ValidateHook(hook.Info{
   464  		Kind:      hooks.StorageAttached,
   465  		StorageId: s.storageTag1.Id(),
   466  	})
   467  	c.Assert(err, jc.ErrorIsNil)
   468  	c.Assert(s.att.Pending(), gc.Equals, 1)
   469  
   470  	s.attachmentsByTag[s.storageTag1].Life = params.Dying
   471  	err = s.att.UpdateStorage([]names.StorageTag{s.storageTag1})
   472  	c.Assert(err, jc.ErrorIsNil)
   473  	err = s.att.ValidateHook(hook.Info{
   474  		Kind:      hooks.StorageAttached,
   475  		StorageId: s.storageTag1.Id(),
   476  	})
   477  	c.Assert(err, gc.ErrorMatches, `unknown storage "data/1"`)
   478  	c.Assert(s.att.Pending(), gc.Equals, 0)
   479  	assertStorageTags(c, s.att)
   480  }