github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/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  	"github.com/juju/charm/v12/hooks"
     8  	"github.com/juju/loggo"
     9  	"github.com/juju/names/v5"
    10  	jc "github.com/juju/testing/checkers"
    11  	"go.uber.org/mock/gomock"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	"github.com/juju/juju/core/life"
    15  	"github.com/juju/juju/core/model"
    16  	"github.com/juju/juju/rpc/params"
    17  	"github.com/juju/juju/testing"
    18  	"github.com/juju/juju/worker/uniter/hook"
    19  	"github.com/juju/juju/worker/uniter/operation"
    20  	"github.com/juju/juju/worker/uniter/remotestate"
    21  	"github.com/juju/juju/worker/uniter/resolver"
    22  	"github.com/juju/juju/worker/uniter/storage"
    23  )
    24  
    25  type attachmentsSuite struct {
    26  	testing.BaseSuite
    27  	mockStateOpsSuite
    28  
    29  	modelType model.ModelType
    30  }
    31  
    32  type caasAttachmentsSuite struct {
    33  	attachmentsSuite
    34  }
    35  
    36  type iaasAttachmentsSuite struct {
    37  	attachmentsSuite
    38  }
    39  
    40  var _ = gc.Suite(&caasAttachmentsSuite{})
    41  var _ = gc.Suite(&iaasAttachmentsSuite{})
    42  
    43  func (s *attachmentsSuite) SetUpTest(c *gc.C) {
    44  	s.BaseSuite.SetUpTest(c)
    45  	s.storSt = storage.NewState()
    46  }
    47  
    48  func (s *caasAttachmentsSuite) SetUpTest(c *gc.C) {
    49  	s.modelType = model.CAAS
    50  	s.attachmentsSuite.SetUpTest(c)
    51  }
    52  
    53  func (s *iaasAttachmentsSuite) SetUpTest(c *gc.C) {
    54  	s.modelType = model.IAAS
    55  	s.attachmentsSuite.SetUpTest(c)
    56  }
    57  
    58  func (s *attachmentsSuite) setupMocks(c *gc.C) *gomock.Controller {
    59  	ctlr := s.mockStateOpsSuite.setupMocks(c)
    60  	s.expectState(c)
    61  	return ctlr
    62  }
    63  
    64  func (s *attachmentsSuite) TestNewAttachments(c *gc.C) {
    65  	defer s.setupMocks(c).Finish()
    66  
    67  	unitTag := names.NewUnitTag("mysql/0")
    68  	abort := make(chan struct{})
    69  	st := &mockStorageAccessor{
    70  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
    71  			c.Assert(u, gc.Equals, unitTag)
    72  			return nil, nil
    73  		},
    74  	}
    75  
    76  	_, err := storage.NewAttachments(st, unitTag, s.mockStateOps, abort)
    77  	c.Assert(err, jc.ErrorIsNil)
    78  }
    79  
    80  func (s *attachmentsSuite) assertNewAttachments(c *gc.C, storageTag names.StorageTag) *storage.Attachments {
    81  	unitTag := names.NewUnitTag("mysql/0")
    82  	abort := make(chan struct{})
    83  
    84  	// Simulate remote State returning a single Alive storage attachment.
    85  
    86  	attachmentIds := []params.StorageAttachmentId{{
    87  		StorageTag: storageTag.String(),
    88  		UnitTag:    unitTag.String(),
    89  	}}
    90  	attachment := params.StorageAttachment{
    91  		StorageTag: storageTag.String(),
    92  		UnitTag:    unitTag.String(),
    93  		Life:       life.Alive,
    94  		Kind:       params.StorageKindBlock,
    95  		Location:   "/dev/sdb",
    96  	}
    97  
    98  	storSt := &mockStorageAccessor{
    99  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   100  			c.Assert(u, gc.Equals, unitTag)
   101  			return attachmentIds, nil
   102  		},
   103  		storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) {
   104  			c.Assert(s, gc.Equals, storageTag)
   105  			return attachment, nil
   106  		},
   107  	}
   108  
   109  	att, err := storage.NewAttachments(storSt, unitTag, s.mockStateOps, abort)
   110  	c.Assert(err, jc.ErrorIsNil)
   111  	return att
   112  }
   113  
   114  func (s *attachmentsSuite) TestNewAttachmentsInitHavePending(c *gc.C) {
   115  	defer s.setupMocks(c).Finish()
   116  
   117  	storageTag := names.NewStorageTag("data/0")
   118  
   119  	// No initial storage State, so no storagers will be started.
   120  	att := s.assertNewAttachments(c, storageTag)
   121  	c.Assert(att.Pending(), gc.Equals, 1)
   122  	err := att.ValidateHook(hook.Info{
   123  		Kind:      hooks.StorageAttached,
   124  		StorageId: storageTag.Id(),
   125  	})
   126  	c.Assert(err, jc.ErrorIsNil)
   127  }
   128  
   129  func (s *attachmentsSuite) TestNewAttachmentsInit(c *gc.C) {
   130  	defer s.mockStateOpsSuite.setupMocks(c).Finish()
   131  	storageTag := names.NewStorageTag("data/0")
   132  	s.storSt.Attach(storageTag.Id())
   133  	s.expectSetState(c, "")
   134  	// Setup a storage tag which should be ignored by init.
   135  	s.storSt.Attach("data/3")
   136  	err := s.storSt.Detach("data/3")
   137  	c.Assert(err, jc.ErrorIsNil)
   138  	s.expectState(c)
   139  
   140  	att := s.assertNewAttachments(c, storageTag)
   141  	c.Assert(att.Pending(), gc.Equals, 0)
   142  }
   143  
   144  func (s *attachmentsSuite) TestAttachmentsUpdateShortCircuitDeath(c *gc.C) {
   145  	defer s.setupMocks(c).Finish()
   146  
   147  	abort := make(chan struct{})
   148  
   149  	unitTag := names.NewUnitTag("mysql/0")
   150  	removed := names.NewSet()
   151  	st := &mockStorageAccessor{
   152  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   153  			return nil, nil
   154  		},
   155  		remove: func(s names.StorageTag, u names.UnitTag) error {
   156  			c.Assert(u, gc.Equals, unitTag)
   157  			removed.Add(s)
   158  			return nil
   159  		},
   160  	}
   161  
   162  	att, err := storage.NewAttachments(st, unitTag, s.mockStateOps, abort)
   163  	c.Assert(err, jc.ErrorIsNil)
   164  	r := storage.NewResolver(loggo.GetLogger("test"), att, s.modelType)
   165  
   166  	// First make sure we create a storage-attached hook operation for
   167  	// data/0. We do this to show that until the hook is *committed*,
   168  	// we will still short-circuit removal.
   169  	localState := resolver.LocalState{State: operation.State{
   170  		Kind: operation.Continue,
   171  	}}
   172  	storageTag0 := names.NewStorageTag("data/0")
   173  	storageTag1 := names.NewStorageTag("data/1")
   174  	_, err = r.NextOp(localState, remotestate.Snapshot{
   175  		Life: life.Alive,
   176  		Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   177  			storageTag0: {
   178  				Life:     life.Alive,
   179  				Kind:     params.StorageKindBlock,
   180  				Location: "/dev/sdb",
   181  				Attached: true,
   182  			},
   183  		},
   184  	}, &mockOperations{})
   185  	c.Assert(err, jc.ErrorIsNil)
   186  
   187  	for _, storageTag := range []names.StorageTag{storageTag0, storageTag1} {
   188  		_, err = r.NextOp(localState, remotestate.Snapshot{
   189  			Life: life.Alive,
   190  			Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   191  				storageTag: {Life: life.Dying},
   192  			},
   193  		}, nil)
   194  		c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   195  	}
   196  	c.Assert(removed.SortedValues(), jc.DeepEquals, []names.Tag{
   197  		storageTag0, storageTag1,
   198  	})
   199  }
   200  
   201  func (s *attachmentsSuite) TestAttachmentsStorage(c *gc.C) {
   202  	s.testAttachmentsStorage(c, operation.State{Kind: operation.Continue})
   203  }
   204  
   205  func (s *caasAttachmentsSuite) TestAttachmentsStorageStarted(c *gc.C) {
   206  	opState := operation.State{
   207  		Kind:      operation.RunHook,
   208  		Step:      operation.Queued,
   209  		Installed: true,
   210  		Started:   true,
   211  	}
   212  	s.testAttachmentsStorage(c, opState)
   213  }
   214  
   215  func (s *attachmentsSuite) testAttachmentsStorage(c *gc.C, opState operation.State) {
   216  	defer s.setupMocks(c).Finish()
   217  
   218  	unitTag := names.NewUnitTag("mysql/0")
   219  	abort := make(chan struct{})
   220  
   221  	st := &mockStorageAccessor{
   222  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   223  			return nil, nil
   224  		},
   225  	}
   226  
   227  	att, err := storage.NewAttachments(st, unitTag, s.mockStateOps, abort)
   228  	c.Assert(err, jc.ErrorIsNil)
   229  	r := storage.NewResolver(loggo.GetLogger("test"), att, s.modelType)
   230  
   231  	storageTag := names.NewStorageTag("data/0")
   232  
   233  	// Inform the resolver of an attachment.
   234  	localState := resolver.LocalState{State: opState}
   235  	op, err := r.NextOp(localState, remotestate.Snapshot{
   236  		Life: life.Alive,
   237  		Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   238  			storageTag: {
   239  				Kind:     params.StorageKindBlock,
   240  				Life:     life.Alive,
   241  				Location: "/dev/sdb",
   242  				Attached: true,
   243  			},
   244  		},
   245  	}, &mockOperations{})
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	c.Assert(op.String(), gc.Equals, "run hook storage-attached")
   248  }
   249  
   250  func (s *caasAttachmentsSuite) TestAttachmentsStorageNotStarted(c *gc.C) {
   251  	defer s.setupMocks(c).Finish()
   252  
   253  	unitTag := names.NewUnitTag("mysql/0")
   254  	abort := make(chan struct{})
   255  
   256  	st := &mockStorageAccessor{
   257  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   258  			return nil, nil
   259  		},
   260  	}
   261  
   262  	att, err := storage.NewAttachments(st, unitTag, s.mockStateOps, abort)
   263  	c.Assert(err, jc.ErrorIsNil)
   264  	r := storage.NewResolver(loggo.GetLogger("test"), att, s.modelType)
   265  
   266  	storageTag := names.NewStorageTag("data/0")
   267  
   268  	// Inform the resolver of an attachment.
   269  	localState := resolver.LocalState{State: operation.State{
   270  		Kind:      operation.RunHook,
   271  		Step:      operation.Queued,
   272  		Installed: true,
   273  		Started:   false,
   274  	}}
   275  	_, err = r.NextOp(localState, remotestate.Snapshot{
   276  		Life: life.Alive,
   277  		Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   278  			storageTag: {
   279  				Kind:     params.StorageKindBlock,
   280  				Life:     life.Alive,
   281  				Location: "/dev/sdb",
   282  				Attached: true,
   283  			},
   284  		},
   285  	}, &mockOperations{})
   286  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   287  }
   288  
   289  func (s *attachmentsSuite) TestAttachmentsCommitHook(c *gc.C) {
   290  	defer s.setupMocks(c).Finish()
   291  
   292  	unitTag := names.NewUnitTag("mysql/0")
   293  	abort := make(chan struct{})
   294  
   295  	var removed bool
   296  	storageTag := names.NewStorageTag("data/0")
   297  	st := &mockStorageAccessor{
   298  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   299  			return nil, nil
   300  		},
   301  		remove: func(s names.StorageTag, u names.UnitTag) error {
   302  			removed = true
   303  			c.Assert(s, gc.Equals, storageTag)
   304  			return nil
   305  		},
   306  	}
   307  
   308  	att, err := storage.NewAttachments(st, unitTag, s.mockStateOps, abort)
   309  	c.Assert(err, jc.ErrorIsNil)
   310  	r := storage.NewResolver(loggo.GetLogger("test"), att, s.modelType)
   311  
   312  	// Inform the resolver of an attachment.
   313  	localState := resolver.LocalState{State: operation.State{
   314  		Kind: operation.Continue,
   315  	}}
   316  	_, err = r.NextOp(localState, remotestate.Snapshot{
   317  		Life: life.Alive,
   318  		Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   319  			storageTag: {
   320  				Kind:     params.StorageKindBlock,
   321  				Life:     life.Alive,
   322  				Location: "/dev/sdb",
   323  				Attached: true,
   324  			},
   325  		},
   326  	}, &mockOperations{})
   327  	c.Assert(err, jc.ErrorIsNil)
   328  	c.Assert(att.Pending(), gc.Equals, 1)
   329  
   330  	s.storSt.Attach(storageTag.Id())
   331  	s.expectSetState(c, "")
   332  	err = att.CommitHook(hook.Info{
   333  		Kind:      hooks.StorageAttached,
   334  		StorageId: storageTag.Id(),
   335  	})
   336  	c.Assert(err, jc.ErrorIsNil)
   337  
   338  	err = s.storSt.Detach(storageTag.Id())
   339  	c.Assert(err, jc.ErrorIsNil)
   340  	s.expectSetState(c, "")
   341  	c.Assert(removed, jc.IsFalse)
   342  	err = att.CommitHook(hook.Info{
   343  		Kind:      hooks.StorageDetaching,
   344  		StorageId: storageTag.Id(),
   345  	})
   346  	c.Assert(err, jc.ErrorIsNil)
   347  	c.Assert(removed, jc.IsTrue)
   348  }
   349  
   350  func (s *attachmentsSuite) TestAttachmentsSetDying(c *gc.C) {
   351  	defer s.setupMocks(c).Finish()
   352  
   353  	unitTag := names.NewUnitTag("mysql/0")
   354  	abort := make(chan struct{})
   355  
   356  	storageTag := names.NewStorageTag("data/0")
   357  	var destroyed, removed bool
   358  	st := &mockStorageAccessor{
   359  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   360  			c.Assert(u, gc.Equals, unitTag)
   361  			return []params.StorageAttachmentId{{
   362  				StorageTag: storageTag.String(),
   363  				UnitTag:    unitTag.String(),
   364  			}}, nil
   365  		},
   366  		storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) {
   367  			c.Assert(u, gc.Equals, unitTag)
   368  			c.Assert(s, gc.Equals, storageTag)
   369  			return params.StorageAttachment{}, &params.Error{
   370  				Message: "not provisioned",
   371  				Code:    params.CodeNotProvisioned,
   372  			}
   373  		},
   374  		destroyUnitStorageAttachments: func(u names.UnitTag) error {
   375  			c.Assert(u, gc.Equals, unitTag)
   376  			destroyed = true
   377  			return nil
   378  		},
   379  		remove: func(s names.StorageTag, u names.UnitTag) error {
   380  			c.Assert(removed, jc.IsFalse)
   381  			c.Assert(s, gc.Equals, storageTag)
   382  			c.Assert(u, gc.Equals, unitTag)
   383  			removed = true
   384  			return nil
   385  		},
   386  	}
   387  
   388  	att, err := storage.NewAttachments(st, unitTag, s.mockStateOps, abort)
   389  	c.Assert(err, jc.ErrorIsNil)
   390  	c.Assert(att.Pending(), gc.Equals, 1)
   391  	r := storage.NewResolver(loggo.GetLogger("test"), att, s.modelType)
   392  
   393  	// Inform the resolver that the unit is Dying. The storage is still
   394  	// Alive, and is now provisioned, but will be destroyed and removed
   395  	// by the resolver.
   396  	localState := resolver.LocalState{State: operation.State{
   397  		Kind: operation.Continue,
   398  	}}
   399  	_, err = r.NextOp(localState, remotestate.Snapshot{
   400  		Life: life.Dying,
   401  		Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   402  			storageTag: {
   403  				Kind:     params.StorageKindBlock,
   404  				Life:     life.Alive,
   405  				Location: "/dev/sdb",
   406  				Attached: true,
   407  			},
   408  		},
   409  	}, &mockOperations{})
   410  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   411  	c.Assert(destroyed, jc.IsTrue)
   412  	c.Assert(att.Pending(), gc.Equals, 0)
   413  	c.Assert(removed, jc.IsTrue)
   414  }
   415  
   416  func (s *attachmentsSuite) TestAttachmentsWaitPending(c *gc.C) {
   417  	defer s.setupMocks(c).Finish()
   418  
   419  	unitTag := names.NewUnitTag("mysql/0")
   420  	abort := make(chan struct{})
   421  
   422  	storageTag := names.NewStorageTag("data/0")
   423  	st := &mockStorageAccessor{
   424  		unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
   425  			return nil, nil
   426  		},
   427  	}
   428  
   429  	att, err := storage.NewAttachments(st, unitTag, s.mockStateOps, abort)
   430  	c.Assert(err, jc.ErrorIsNil)
   431  	r := storage.NewResolver(loggo.GetLogger("test"), att, s.modelType)
   432  
   433  	nextOp := func(installed bool) error {
   434  		localState := resolver.LocalState{State: operation.State{
   435  			Installed: installed,
   436  			Kind:      operation.Continue,
   437  		}}
   438  		_, err := r.NextOp(localState, remotestate.Snapshot{
   439  			Life: life.Alive,
   440  			Storage: map[names.StorageTag]remotestate.StorageSnapshot{
   441  				storageTag: {
   442  					Life:     life.Alive,
   443  					Attached: false,
   444  				},
   445  			},
   446  		}, &mockOperations{})
   447  		return err
   448  	}
   449  
   450  	// Inform the resolver of a new, unprovisioned storage attachment.
   451  	// For IAAS models, before install, we should wait for its completion;
   452  	// after install, we should not.
   453  	err = nextOp(false /* workload not installed */)
   454  	c.Assert(att.Pending(), gc.Equals, 1)
   455  
   456  	if s.modelType == model.IAAS {
   457  		c.Assert(err, gc.Equals, resolver.ErrWaiting)
   458  	} else {
   459  		c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   460  	}
   461  
   462  	err = nextOp(true /* workload installed */)
   463  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   464  }