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