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