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