github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/storage/source_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  	"time"
     8  
     9  	"github.com/juju/names"
    10  	jc "github.com/juju/testing/checkers"
    11  	gc "gopkg.in/check.v1"
    12  	"gopkg.in/juju/charm.v5/hooks"
    13  
    14  	"github.com/juju/juju/api/watcher"
    15  	"github.com/juju/juju/apiserver/params"
    16  	corestorage "github.com/juju/juju/storage"
    17  	"github.com/juju/juju/testing"
    18  	"github.com/juju/juju/worker/uniter/hook"
    19  	"github.com/juju/juju/worker/uniter/storage"
    20  )
    21  
    22  const initiallyUnattached = false
    23  const initiallyAttached = true
    24  
    25  type storageHookQueueSuite struct {
    26  	testing.BaseSuite
    27  }
    28  
    29  var _ = gc.Suite(&storageHookQueueSuite{})
    30  
    31  func newHookQueue(attached bool) storage.StorageHookQueue {
    32  	return storage.NewStorageHookQueue(
    33  		names.NewUnitTag("mysql/0"),
    34  		names.NewStorageTag("data/0"),
    35  		attached,
    36  	)
    37  }
    38  
    39  func updateHookQueue(c *gc.C, q storage.StorageHookQueue, life params.Life) {
    40  	err := q.Update(params.StorageAttachment{
    41  		Life:     life,
    42  		Kind:     params.StorageKindBlock,
    43  		Location: "/dev/sdb",
    44  	})
    45  	c.Assert(err, jc.ErrorIsNil)
    46  }
    47  
    48  func (s *storageHookQueueSuite) TestStorageHookQueueAttachedHook(c *gc.C) {
    49  	q := newHookQueue(initiallyUnattached)
    50  	updateHookQueue(c, q, params.Alive)
    51  	c.Assert(q.Empty(), jc.IsFalse)
    52  	c.Assert(q.Next(), gc.Equals, hook.Info{
    53  		Kind:      hooks.StorageAttached,
    54  		StorageId: "data/0",
    55  	})
    56  }
    57  
    58  func (s *storageHookQueueSuite) TestStorageHookQueueAlreadyAttached(c *gc.C) {
    59  	q := newHookQueue(initiallyAttached)
    60  	updateHookQueue(c, q, params.Alive)
    61  	// Already attached, so no hooks should have been queued.
    62  	c.Assert(q.Empty(), jc.IsTrue)
    63  }
    64  
    65  func (s *storageHookQueueSuite) TestStorageHookQueueAttachedDetach(c *gc.C) {
    66  	q := newHookQueue(initiallyAttached)
    67  	updateHookQueue(c, q, params.Dying)
    68  	c.Assert(q.Empty(), jc.IsFalse)
    69  	c.Assert(q.Next(), gc.Equals, hook.Info{
    70  		Kind:      hooks.StorageDetaching,
    71  		StorageId: "data/0",
    72  	})
    73  }
    74  
    75  func (s *storageHookQueueSuite) TestStorageHookQueueUnattachedDetach(c *gc.C) {
    76  	q := newHookQueue(initiallyUnattached)
    77  	updateHookQueue(c, q, params.Dying)
    78  	// the storage wasn't attached, so Dying short-circuits.
    79  	c.Assert(q.Empty(), jc.IsTrue)
    80  }
    81  
    82  func (s *storageHookQueueSuite) TestStorageHookQueueAttachedUnconsumedDetach(c *gc.C) {
    83  	q := newHookQueue(initiallyUnattached)
    84  	updateHookQueue(c, q, params.Alive)
    85  	c.Assert(q.Next(), gc.Equals, hook.Info{
    86  		Kind:      hooks.StorageAttached,
    87  		StorageId: "data/0",
    88  	})
    89  	// don't consume the storage-attached hook; it should then be unqueued
    90  	updateHookQueue(c, q, params.Dying)
    91  	// since the storage-attached hook wasn't consumed, Dying short-circuits.
    92  	c.Assert(q.Empty(), jc.IsTrue)
    93  }
    94  
    95  func (s *storageHookQueueSuite) TestStorageHookQueueAttachDetach(c *gc.C) {
    96  	q := newHookQueue(initiallyUnattached)
    97  	updateHookQueue(c, q, params.Alive)
    98  	q.Pop()
    99  	updateHookQueue(c, q, params.Dying)
   100  	c.Assert(q.Empty(), jc.IsFalse)
   101  	c.Assert(q.Next(), gc.Equals, hook.Info{
   102  		Kind:      hooks.StorageDetaching,
   103  		StorageId: "data/0",
   104  	})
   105  }
   106  
   107  func (s *storageHookQueueSuite) TestStorageHookQueueDead(c *gc.C) {
   108  	q := newHookQueue(initiallyAttached)
   109  	updateHookQueue(c, q, params.Dying)
   110  	q.Pop()
   111  	updateHookQueue(c, q, params.Dead)
   112  	// Dead does not cause any hook to be queued.
   113  	c.Assert(q.Empty(), jc.IsTrue)
   114  }
   115  
   116  func (s *storageHookQueueSuite) TestStorageHookQueueContext(c *gc.C) {
   117  	q := newHookQueue(initiallyUnattached)
   118  	_, ok := q.Context()
   119  	c.Assert(ok, jc.IsFalse)
   120  
   121  	err := q.Update(params.StorageAttachment{
   122  		Life:     params.Alive,
   123  		Kind:     params.StorageKindFilesystem,
   124  		Location: "/srv",
   125  	})
   126  	c.Assert(err, jc.ErrorIsNil)
   127  	c.Assert(q.Empty(), jc.IsFalse)
   128  
   129  	ctx, ok := q.Context()
   130  	c.Assert(ok, jc.IsTrue)
   131  	c.Assert(ctx, gc.NotNil)
   132  	c.Assert(ctx.Tag(), gc.Equals, names.NewStorageTag("data/0"))
   133  	c.Assert(ctx.Kind(), gc.Equals, corestorage.StorageKindFilesystem)
   134  	c.Assert(ctx.Location(), gc.Equals, "/srv")
   135  }
   136  
   137  func (s *storageHookQueueSuite) TestStorageHookQueueEmpty(c *gc.C) {
   138  	q := newHookQueue(initiallyAttached)
   139  	c.Assert(q.Empty(), jc.IsTrue)
   140  	c.Assert(q.Next, gc.PanicMatches, "source is empty")
   141  	c.Assert(q.Pop, gc.PanicMatches, "source is empty")
   142  }
   143  
   144  func (s *storageHookQueueSuite) TestStorageSourceStop(c *gc.C) {
   145  	unitTag := names.NewUnitTag("mysql/0")
   146  	storageTag := names.NewStorageTag("data/0")
   147  
   148  	// Simulate remote state returning a single Alive storage attachment.
   149  	st := &mockStorageAccessor{
   150  		watchStorageAttachment: func(s names.StorageTag, u names.UnitTag) (watcher.NotifyWatcher, error) {
   151  			return newMockNotifyWatcher(), nil
   152  		},
   153  	}
   154  
   155  	const initiallyUnattached = false
   156  	source, err := storage.NewStorageSource(st, unitTag, storageTag, initiallyUnattached)
   157  	c.Assert(err, jc.ErrorIsNil)
   158  	err = source.Stop()
   159  	c.Assert(err, jc.ErrorIsNil)
   160  }
   161  
   162  func (s *storageHookQueueSuite) TestStorageSourceUpdateErrors(c *gc.C) {
   163  	unitTag := names.NewUnitTag("mysql/0")
   164  	storageTag := names.NewStorageTag("data/0")
   165  
   166  	// Simulate remote state returning a single Alive storage attachment.
   167  	var calls int
   168  	w := newMockNotifyWatcher()
   169  	st := &mockStorageAccessor{
   170  		watchStorageAttachment: func(s names.StorageTag, u names.UnitTag) (watcher.NotifyWatcher, error) {
   171  			return w, nil
   172  		},
   173  		storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) {
   174  			calls++
   175  			switch calls {
   176  			case 1:
   177  				return params.StorageAttachment{}, &params.Error{Code: params.CodeNotFound}
   178  			case 2:
   179  				return params.StorageAttachment{}, &params.Error{Code: params.CodeNotProvisioned}
   180  			case 3:
   181  				// This error should cause the source to stop with an error.
   182  				return params.StorageAttachment{}, &params.Error{
   183  					Code:    params.CodeUnauthorized,
   184  					Message: "unauthorized",
   185  				}
   186  			}
   187  			panic("unexpected call to StorageAttachment")
   188  		},
   189  	}
   190  
   191  	const initiallyUnattached = false
   192  	source, err := storage.NewStorageSource(st, unitTag, storageTag, initiallyUnattached)
   193  	c.Assert(err, jc.ErrorIsNil)
   194  
   195  	assertNoSourceChange := func() {
   196  		select {
   197  		case <-source.Changes():
   198  			c.Fatal("unexpected source change")
   199  		case <-time.After(testing.ShortWait):
   200  		}
   201  	}
   202  	waitSourceChange := func() hook.SourceChange {
   203  		select {
   204  		case ch, ok := <-source.Changes():
   205  			c.Assert(ok, jc.IsTrue)
   206  			assertNoSourceChange()
   207  			return ch
   208  		case <-time.After(testing.LongWait):
   209  			c.Fatal("timed out waiting for source change")
   210  			panic("unreachable")
   211  		}
   212  	}
   213  
   214  	assertNoSourceChange()
   215  
   216  	// First change is "NotFound": not an error.
   217  	w.changes <- struct{}{}
   218  	change := waitSourceChange()
   219  	c.Assert(change(), jc.ErrorIsNil)
   220  
   221  	// Second change is "NotProvisioned": not an error.
   222  	w.changes <- struct{}{}
   223  	change = waitSourceChange()
   224  	c.Assert(change(), jc.ErrorIsNil)
   225  
   226  	// Third change is "Unauthorized": this *is* an error.
   227  	w.changes <- struct{}{}
   228  	change = waitSourceChange()
   229  	c.Assert(change(), gc.ErrorMatches, "refreshing storage details: unauthorized")
   230  }