github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/meterstatus/connected_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package meterstatus_test
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	"go.uber.org/mock/gomock"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	coretesting "github.com/juju/juju/testing"
    18  	"github.com/juju/juju/worker/common/charmrunner"
    19  	"github.com/juju/juju/worker/meterstatus"
    20  	"github.com/juju/juju/worker/meterstatus/mocks"
    21  )
    22  
    23  type ConnectedWorkerSuite struct {
    24  	coretesting.BaseSuite
    25  
    26  	stub *testing.Stub
    27  
    28  	msClient *stubMeterStatusClient
    29  	config   meterstatus.ConnectedConfig
    30  }
    31  
    32  var _ = gc.Suite(&ConnectedWorkerSuite{})
    33  
    34  func (s *ConnectedWorkerSuite) SetUpTest(c *gc.C) {
    35  	s.BaseSuite.SetUpTest(c)
    36  	s.stub = &testing.Stub{}
    37  
    38  	s.msClient = newStubMeterStatusClient(s.stub)
    39  
    40  	s.config = meterstatus.ConnectedConfig{
    41  		Runner:          &stubRunner{stub: s.stub},
    42  		Status:          s.msClient,
    43  		StateReadWriter: struct{ meterstatus.StateReadWriter }{},
    44  		Logger:          loggo.GetLogger("test"),
    45  	}
    46  }
    47  
    48  func assertSignal(c *gc.C, signal <-chan struct{}) {
    49  	select {
    50  	case <-signal:
    51  	case <-time.After(coretesting.LongWait):
    52  		c.Fatal("timed out waiting for signal")
    53  	}
    54  }
    55  
    56  func (s *ConnectedWorkerSuite) TestConfigValid(c *gc.C) {
    57  	c.Assert(s.config.Validate(), jc.ErrorIsNil)
    58  }
    59  
    60  func (s *ConnectedWorkerSuite) TestConfigMissingRunner(c *gc.C) {
    61  	s.config.Runner = nil
    62  	err := s.config.Validate()
    63  	c.Assert(err, jc.Satisfies, errors.IsNotValid)
    64  	c.Assert(err.Error(), gc.Equals, "missing Runner not valid")
    65  }
    66  
    67  func (s *ConnectedWorkerSuite) TestConfigMissingStateReadWriter(c *gc.C) {
    68  	s.config.StateReadWriter = nil
    69  	err := s.config.Validate()
    70  	c.Assert(err, jc.Satisfies, errors.IsNotValid)
    71  	c.Assert(err.Error(), gc.Equals, "missing StateReadWriter not valid")
    72  }
    73  
    74  func (s *ConnectedWorkerSuite) TestConfigMissingStatus(c *gc.C) {
    75  	s.config.Status = nil
    76  	err := s.config.Validate()
    77  	c.Assert(err, jc.Satisfies, errors.IsNotValid)
    78  	c.Assert(err.Error(), gc.Equals, "missing Status not valid")
    79  }
    80  
    81  func (s *ConnectedWorkerSuite) TestConfigMissingLogger(c *gc.C) {
    82  	s.config.Logger = nil
    83  	err := s.config.Validate()
    84  	c.Assert(err, jc.Satisfies, errors.IsNotValid)
    85  	c.Assert(err.Error(), gc.Equals, "missing Logger not valid")
    86  }
    87  
    88  // TestStatusHandlerDoesNotRerunNoChange ensures that the handler does not execute the hook if it
    89  // detects no actual meter status change.
    90  func (s *ConnectedWorkerSuite) TestStatusHandlerDoesNotRerunNoChange(c *gc.C) {
    91  	ctrl := gomock.NewController(c)
    92  	defer ctrl.Finish()
    93  
    94  	stateReadWriter := mocks.NewMockStateReadWriter(ctrl)
    95  	gomock.InOrder(
    96  		stateReadWriter.EXPECT().Read().Return(nil, errors.NotFoundf("no state")),
    97  		stateReadWriter.EXPECT().Write(&meterstatus.State{
    98  			Code: "GREEN",
    99  		}).Return(nil),
   100  	)
   101  
   102  	config := s.config
   103  	config.StateReadWriter = stateReadWriter
   104  	handler, err := meterstatus.NewConnectedStatusHandler(config)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	c.Assert(handler, gc.NotNil)
   107  	_, err = handler.SetUp()
   108  	c.Assert(err, jc.ErrorIsNil)
   109  
   110  	err = handler.Handle(nil)
   111  	c.Assert(err, jc.ErrorIsNil)
   112  	err = handler.Handle(nil)
   113  	c.Assert(err, jc.ErrorIsNil)
   114  
   115  	s.stub.CheckCallNames(c, "WatchMeterStatus", "MeterStatus", "RunHook", "MeterStatus")
   116  }
   117  
   118  // TestStatusHandlerRunsHookOnChanges ensures that the handler runs the meter-status-changed hook
   119  // if an actual meter status change is detected.
   120  func (s *ConnectedWorkerSuite) TestStatusHandlerRunsHookOnChanges(c *gc.C) {
   121  	ctrl := gomock.NewController(c)
   122  	defer ctrl.Finish()
   123  
   124  	stateReadWriter := mocks.NewMockStateReadWriter(ctrl)
   125  	gomock.InOrder(
   126  		stateReadWriter.EXPECT().Read().Return(nil, errors.NotFoundf("no state")),
   127  		// First Handle() invocation
   128  		stateReadWriter.EXPECT().Write(&meterstatus.State{
   129  			Code: "GREEN",
   130  		}).Return(nil),
   131  		// Second Handle() invocation
   132  		stateReadWriter.EXPECT().Write(&meterstatus.State{
   133  			Code: "RED",
   134  		}).Return(nil),
   135  	)
   136  
   137  	config := s.config
   138  	config.StateReadWriter = stateReadWriter
   139  	handler, err := meterstatus.NewConnectedStatusHandler(config)
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	c.Assert(handler, gc.NotNil)
   142  	_, err = handler.SetUp()
   143  	c.Assert(err, jc.ErrorIsNil)
   144  
   145  	handler.Handle(nil)
   146  	s.msClient.SetStatus("RED")
   147  	handler.Handle(nil)
   148  
   149  	c.Assert(err, jc.ErrorIsNil)
   150  	s.stub.CheckCallNames(c, "WatchMeterStatus", "MeterStatus", "RunHook", "MeterStatus", "RunHook")
   151  }
   152  
   153  // TestStatusHandlerHandlesHookMissingError tests that the handler does not report errors
   154  // caused by a missing meter-status-changed hook.
   155  func (s *ConnectedWorkerSuite) TestStatusHandlerHandlesHookMissingError(c *gc.C) {
   156  	ctrl := gomock.NewController(c)
   157  	defer ctrl.Finish()
   158  
   159  	stateReadWriter := mocks.NewMockStateReadWriter(ctrl)
   160  	gomock.InOrder(
   161  		stateReadWriter.EXPECT().Read().Return(nil, errors.NotFoundf("no state")),
   162  		stateReadWriter.EXPECT().Write(&meterstatus.State{
   163  			Code: "GREEN",
   164  		}).Return(nil),
   165  	)
   166  
   167  	s.stub.SetErrors(charmrunner.NewMissingHookError("meter-status-changed"))
   168  	config := s.config
   169  	config.StateReadWriter = stateReadWriter
   170  	handler, err := meterstatus.NewConnectedStatusHandler(config)
   171  	c.Assert(err, jc.ErrorIsNil)
   172  	c.Assert(handler, gc.NotNil)
   173  	_, err = handler.SetUp()
   174  	c.Assert(err, jc.ErrorIsNil)
   175  
   176  	err = handler.Handle(nil)
   177  	c.Assert(err, jc.ErrorIsNil)
   178  	s.stub.CheckCallNames(c, "WatchMeterStatus", "MeterStatus", "RunHook")
   179  }
   180  
   181  // TestStatusHandlerHandlesRandomHookError tests that the meter status handler does not return
   182  // errors encountered while executing the hook.
   183  func (s *ConnectedWorkerSuite) TestStatusHandlerHandlesRandomHookError(c *gc.C) {
   184  	ctrl := gomock.NewController(c)
   185  	defer ctrl.Finish()
   186  
   187  	stateReadWriter := mocks.NewMockStateReadWriter(ctrl)
   188  	gomock.InOrder(
   189  		stateReadWriter.EXPECT().Read().Return(nil, errors.NotFoundf("no state")),
   190  		stateReadWriter.EXPECT().Write(&meterstatus.State{
   191  			Code: "GREEN",
   192  		}).Return(nil),
   193  	)
   194  
   195  	s.stub.SetErrors(fmt.Errorf("blah"))
   196  	config := s.config
   197  	config.StateReadWriter = stateReadWriter
   198  	handler, err := meterstatus.NewConnectedStatusHandler(config)
   199  	c.Assert(err, jc.ErrorIsNil)
   200  	c.Assert(handler, gc.NotNil)
   201  	_, err = handler.SetUp()
   202  	c.Assert(err, jc.ErrorIsNil)
   203  
   204  	err = handler.Handle(nil)
   205  	c.Assert(err, jc.ErrorIsNil)
   206  
   207  	s.stub.CheckCallNames(c, "WatchMeterStatus", "MeterStatus", "RunHook")
   208  }
   209  
   210  // TestStatusHandlerDoesNotRerunAfterRestart tests that the status handler will not rerun a meter-status-changed
   211  // hook if it is restarted, but no actual changes are recorded.
   212  func (s *ConnectedWorkerSuite) TestStatusHandlerDoesNotRerunAfterRestart(c *gc.C) {
   213  	ctrl := gomock.NewController(c)
   214  	defer ctrl.Finish()
   215  
   216  	stateReadWriter := mocks.NewMockStateReadWriter(ctrl)
   217  	gomock.InOrder(
   218  		// First run
   219  		stateReadWriter.EXPECT().Read().Return(nil, errors.NotFoundf("no state")),
   220  		stateReadWriter.EXPECT().Write(&meterstatus.State{
   221  			Code: "GREEN",
   222  		}).Return(nil),
   223  
   224  		// Second run
   225  		stateReadWriter.EXPECT().Read().Return(&meterstatus.State{
   226  			Code: "GREEN",
   227  		}, nil),
   228  	)
   229  
   230  	config := s.config
   231  	config.StateReadWriter = stateReadWriter
   232  	handler, err := meterstatus.NewConnectedStatusHandler(config)
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	c.Assert(handler, gc.NotNil)
   235  	_, err = handler.SetUp()
   236  	c.Assert(err, jc.ErrorIsNil)
   237  
   238  	err = handler.Handle(nil)
   239  	c.Assert(err, jc.ErrorIsNil)
   240  
   241  	s.stub.CheckCallNames(c, "WatchMeterStatus", "MeterStatus", "RunHook")
   242  	s.stub.ResetCalls()
   243  
   244  	// Create a new handler (imitating worker restart).
   245  	handler, err = meterstatus.NewConnectedStatusHandler(config)
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	c.Assert(handler, gc.NotNil)
   248  	_, err = handler.SetUp()
   249  	c.Assert(err, jc.ErrorIsNil)
   250  
   251  	err = handler.Handle(nil)
   252  	c.Assert(err, jc.ErrorIsNil)
   253  
   254  	s.stub.CheckCallNames(c, "WatchMeterStatus", "MeterStatus")
   255  }