github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/core/presence/presence_test.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package presence_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/clock/testclock"
    10  	jc "github.com/juju/testing/checkers"
    11  	gc "gopkg.in/check.v1"
    12  
    13  	"github.com/juju/juju/core/presence"
    14  )
    15  
    16  type suite struct{}
    17  
    18  var _ = gc.Suite(&suite{})
    19  
    20  func (*suite) assertEmptyConnections(c *gc.C, connections presence.Connections) {
    21  	c.Assert(connections.Count(), gc.Equals, 0)
    22  	c.Assert(connections.Models(), gc.HasLen, 0)
    23  	c.Assert(connections.Servers(), gc.HasLen, 0)
    24  	c.Assert(connections.Agents(), gc.HasLen, 0)
    25  	c.Assert(connections.Values(), gc.HasLen, 0)
    26  }
    27  
    28  func (*suite) assertConnections(c *gc.C, connections presence.Connections, expected []presence.Value) {
    29  	c.Assert(connections.Values(), jc.SameContents, expected)
    30  }
    31  
    32  func (s *suite) TestEmptyRecorder(c *gc.C) {
    33  	r := presence.New(testclock.NewClock(time.Time{}))
    34  	c.Assert(r.IsEnabled(), jc.IsFalse)
    35  	r.Enable()
    36  	s.assertEmptyConnections(c, r.Connections())
    37  }
    38  
    39  func (s *suite) TestBootstrapCase(c *gc.C) {
    40  	r, _ := bootstrap()
    41  
    42  	c.Assert(r.IsEnabled(), jc.IsTrue)
    43  
    44  	connections := r.Connections()
    45  	expected := []presence.Value{alive(ha0)}
    46  
    47  	s.assertConnections(c, connections, expected)
    48  	s.assertConnections(c, connections.ForModel(bootstrapUUID), expected)
    49  	s.assertEmptyConnections(c, connections.ForModel(modelUUID))
    50  }
    51  
    52  func (s *suite) TestHAController(c *gc.C) {
    53  	r, _ := bootstrap()
    54  	enableHA(r)
    55  
    56  	connections := r.Connections()
    57  	expected := []presence.Value{alive(ha0), alive(ha1), alive(ha2)}
    58  
    59  	c.Assert(connections.Values(), jc.DeepEquals, expected)
    60  	s.assertConnections(c, connections.ForModel(bootstrapUUID), expected)
    61  	s.assertEmptyConnections(c, connections.ForModel(modelUUID))
    62  
    63  	s.assertConnections(c, connections.ForServer(ha0.Server), values(alive(ha0)))
    64  	s.assertConnections(c, connections.ForServer(ha1.Server), values(alive(ha1)))
    65  	s.assertConnections(c, connections.ForServer(ha2.Server), values(alive(ha2)))
    66  }
    67  
    68  func (s *suite) TestModels(c *gc.C) {
    69  	r, _ := bootstrap()
    70  	enableHA(r)
    71  	deployModel(r)
    72  
    73  	connections := r.Connections()
    74  	s.assertConnections(c, connections.ForModel(bootstrapUUID),
    75  		values(alive(ha0), alive(ha1), alive(ha2)))
    76  	s.assertConnections(c, connections.ForModel(modelUUID),
    77  		values(alive(modelMachine0), alive(modelMachine1),
    78  			alive(modelUnit1), alive(modelUnit2)))
    79  
    80  	s.assertConnections(c, connections.ForServer(ha0.Server),
    81  		values(alive(ha0), alive(modelUnit1), alive(modelUnit2)))
    82  	s.assertConnections(c, connections.ForServer(ha1.Server),
    83  		values(alive(ha1), alive(modelMachine0)))
    84  	s.assertConnections(c, connections.ForServer(ha2.Server),
    85  		values(alive(ha2), alive(modelMachine1)))
    86  }
    87  
    88  func (s *suite) TestTimeRecording(c *gc.C) {
    89  	now := time.Now()
    90  	r, clock := bootstrap(now)
    91  
    92  	m0 := lastSeen(alive(ha0), now)
    93  	clock.Advance(time.Minute)
    94  	now = clock.Now()
    95  	enableHA(r)
    96  
    97  	m1 := lastSeen(alive(ha1), now)
    98  	m2 := lastSeen(alive(ha2), now)
    99  
   100  	connections := r.Connections()
   101  	s.assertConnections(c, connections.ForModel(bootstrapUUID),
   102  		values(m0, m1, m2))
   103  }
   104  
   105  func (s *suite) TestActivity(c *gc.C) {
   106  	r, clock := bootstrap()
   107  	enableHA(r)
   108  	deployModel(r)
   109  
   110  	clock.Advance(5 * time.Minute)
   111  
   112  	// Register activity for model machine 0.
   113  	r.Activity("machine-1", 1237)
   114  
   115  	// These connections are all the same except for modelMachine0
   116  	// which shows an updated time.
   117  	mm0 := lastSeen(alive(modelMachine0), clock.Now())
   118  
   119  	connections := r.Connections()
   120  	s.assertConnections(c, connections.ForModel(bootstrapUUID),
   121  		values(alive(ha0), alive(ha1), alive(ha2)))
   122  	s.assertConnections(c, connections.ForModel(modelUUID),
   123  		values(mm0, alive(modelMachine1),
   124  			alive(modelUnit1), alive(modelUnit2)))
   125  
   126  	s.assertConnections(c, connections.ForServer(ha0.Server),
   127  		values(alive(ha0), alive(modelUnit1), alive(modelUnit2)))
   128  	s.assertConnections(c, connections.ForServer(ha1.Server),
   129  		values(alive(ha1), mm0))
   130  	s.assertConnections(c, connections.ForServer(ha2.Server),
   131  		values(alive(ha2), alive(modelMachine1)))
   132  }
   133  
   134  func (s *suite) TestDisconnect(c *gc.C) {
   135  	r, _ := bootstrap()
   136  	enableHA(r)
   137  	deployModel(r)
   138  
   139  	r.Disconnect(ha0.Server, ha0.ConnectionID)
   140  	r.Disconnect(modelUnit1.Server, modelUnit1.ConnectionID)
   141  
   142  	connections := r.Connections()
   143  
   144  	c.Assert(connections.Count(), gc.Equals, 5)
   145  	s.assertConnections(c, connections.ForModel(bootstrapUUID),
   146  		values(alive(ha1), alive(ha2)))
   147  	s.assertConnections(c, connections.ForModel(modelUUID),
   148  		values(alive(modelMachine0), alive(modelMachine1), alive(modelUnit2)))
   149  }
   150  
   151  func (s *suite) TestDisableClears(c *gc.C) {
   152  	r, _ := bootstrap()
   153  	r.Disable()
   154  
   155  	s.assertEmptyConnections(c, r.Connections())
   156  }
   157  
   158  func (s *suite) TestServerDown(c *gc.C) {
   159  	r, _ := bootstrap()
   160  	enableHA(r)
   161  	deployModel(r)
   162  
   163  	r.ServerDown("machine-0")
   164  
   165  	connections := r.Connections()
   166  	s.assertConnections(c, connections.ForModel(bootstrapUUID),
   167  		values(missing(ha0), alive(ha1), alive(ha2)))
   168  	s.assertConnections(c, connections.ForModel(modelUUID),
   169  		values(alive(modelMachine0), alive(modelMachine1),
   170  			missing(modelUnit1), missing(modelUnit2)))
   171  
   172  	s.assertConnections(c, connections.ForServer(ha0.Server),
   173  		values(missing(ha0), missing(modelUnit1), missing(modelUnit2)))
   174  	s.assertConnections(c, connections.ForServer(ha1.Server),
   175  		values(alive(ha1), alive(modelMachine0)))
   176  	s.assertConnections(c, connections.ForServer(ha2.Server),
   177  		values(alive(ha2), alive(modelMachine1)))
   178  }
   179  
   180  func (s *suite) TestServerDownFollowedByConnections(c *gc.C) {
   181  	r, _ := bootstrap()
   182  	enableHA(r)
   183  	deployModel(r)
   184  
   185  	r.ServerDown("machine-0")
   186  	connect(r, ha0)
   187  	connect(r, modelUnit1)
   188  	connect(r, modelUnit2)
   189  
   190  	connections := r.Connections()
   191  	s.assertConnections(c, connections.ForModel(bootstrapUUID),
   192  		values(alive(ha0), alive(ha1), alive(ha2)))
   193  	s.assertConnections(c, connections.ForModel(modelUUID),
   194  		values(alive(modelMachine0), alive(modelMachine1),
   195  			alive(modelUnit1), alive(modelUnit2)))
   196  
   197  	s.assertConnections(c, connections.ForServer(ha0.Server),
   198  		values(alive(ha0), alive(modelUnit1), alive(modelUnit2)))
   199  	s.assertConnections(c, connections.ForServer(ha1.Server),
   200  		values(alive(ha1), alive(modelMachine0)))
   201  	s.assertConnections(c, connections.ForServer(ha2.Server),
   202  		values(alive(ha2), alive(modelMachine1)))
   203  }
   204  
   205  func (s *suite) TestServerDownRaceConnections(c *gc.C) {
   206  	r, _ := bootstrap()
   207  	enableHA(r)
   208  	deployModel(r)
   209  
   210  	// Deal with the situation where machine-0 was marked as
   211  	// down, but before an update for the server comes it, some
   212  	// connections are updated, but the update from the server
   213  	// hadn't processed all the connections yet.
   214  	r.ServerDown("machine-0")
   215  	connect(r, ha0)
   216  	connect(r, modelUnit1)
   217  	err := r.UpdateServer("machine-0", values(ha0))
   218  	c.Assert(err, jc.ErrorIsNil)
   219  
   220  	connections := r.Connections()
   221  	s.assertConnections(c, connections.ForServer(ha0.Server),
   222  		values(alive(ha0), alive(modelUnit1)))
   223  }
   224  
   225  func (s *suite) TestUpdateServer(c *gc.C) {
   226  	r, _ := bootstrap()
   227  	enableHA(r)
   228  	deployModel(r)
   229  
   230  	// Replace machine-0 values with just the ha node.
   231  	// The values need to include the status of the connection.
   232  	r.ServerDown("machine-0")
   233  	err := r.UpdateServer("machine-0", values(ha0))
   234  	c.Assert(err, jc.ErrorIsNil)
   235  
   236  	connections := r.Connections()
   237  	s.assertConnections(c, connections.ForModel(bootstrapUUID),
   238  		values(alive(ha0), alive(ha1), alive(ha2)))
   239  	s.assertConnections(c, connections.ForModel(modelUUID),
   240  		values(alive(modelMachine0), alive(modelMachine1)))
   241  
   242  	s.assertConnections(c, connections.ForServer(ha0.Server),
   243  		values(alive(ha0)))
   244  	s.assertConnections(c, connections.ForServer(ha1.Server),
   245  		values(alive(ha1), alive(modelMachine0)))
   246  	s.assertConnections(c, connections.ForServer(ha2.Server),
   247  		values(alive(ha2), alive(modelMachine1)))
   248  }
   249  
   250  func (s *suite) TestUpdateServerError(c *gc.C) {
   251  	r, _ := bootstrap()
   252  	enableHA(r)
   253  	deployModel(r)
   254  
   255  	err := r.UpdateServer("machine-0", values(alive(ha1)))
   256  	c.Assert(err, gc.ErrorMatches, `connection server mismatch, got "machine-1" expected "machine-0"`)
   257  }
   258  
   259  func (s *suite) TestConnections(c *gc.C) {
   260  	r, _ := bootstrap()
   261  	enableHA(r)
   262  	deployModel(r)
   263  
   264  	r.ServerDown("machine-0")
   265  
   266  	connections := r.Connections()
   267  
   268  	c.Assert(connections.Count(), gc.Equals, 7)
   269  	status, err := connections.AgentStatus("machine-0")
   270  	c.Assert(status, gc.Equals, presence.Unknown)
   271  	c.Assert(err, gc.ErrorMatches, "connections not limited to a model, agent ambiguous")
   272  
   273  	controllerConnections := connections.ForModel(bootstrapUUID)
   274  	c.Assert(controllerConnections.Count(), gc.Equals, 3)
   275  
   276  	status, err = controllerConnections.AgentStatus("machine-0")
   277  	c.Assert(status, gc.Equals, presence.Missing)
   278  	c.Assert(err, jc.ErrorIsNil)
   279  	status, err = controllerConnections.AgentStatus("machine-1")
   280  	c.Assert(status, gc.Equals, presence.Alive)
   281  	c.Assert(err, jc.ErrorIsNil)
   282  	status, err = controllerConnections.AgentStatus("machine-4")
   283  	c.Assert(status, gc.Equals, presence.Unknown)
   284  	c.Assert(err, jc.ErrorIsNil)
   285  }
   286  
   287  func bootstrap(initialTime ...time.Time) (presence.Recorder, *testclock.Clock) {
   288  	if len(initialTime) > 1 {
   289  		panic("initialTime should be zero or one values")
   290  	}
   291  	var t time.Time
   292  	if len(initialTime) > 0 {
   293  		t = initialTime[0]
   294  	}
   295  	// By using a testing clock with a zero time, the times are always empty.
   296  	clock := testclock.NewClock(t)
   297  	r := presence.New(clock)
   298  	r.Enable()
   299  	connect(r, ha0)
   300  	return r, clock
   301  }
   302  
   303  func enableHA(r presence.Recorder) {
   304  	connect(r, ha1)
   305  	connect(r, ha2)
   306  }
   307  
   308  func deployModel(r presence.Recorder) {
   309  	connect(r, modelMachine0)
   310  	connect(r, modelMachine1)
   311  	connect(r, modelUnit1)
   312  	connect(r, modelUnit2)
   313  }
   314  
   315  func values(v ...presence.Value) []presence.Value {
   316  	return v
   317  }
   318  
   319  func alive(v presence.Value) presence.Value {
   320  	v.Status = presence.Alive
   321  	return v
   322  }
   323  
   324  func missing(v presence.Value) presence.Value {
   325  	v.Status = presence.Missing
   326  	return v
   327  }
   328  
   329  func lastSeen(v presence.Value, when time.Time) presence.Value {
   330  	v.LastSeen = when
   331  	return v
   332  }
   333  
   334  func connect(r presence.Recorder, info presence.Value) {
   335  	r.Connect(info.Server, info.Model, info.Agent, info.ConnectionID, info.ControllerAgent, info.UserData)
   336  }
   337  
   338  const bootstrapUUID = "bootstrap-uuid"
   339  const modelUUID = "model-uuid"
   340  
   341  var ha0 = presence.Value{
   342  	Model:        bootstrapUUID,
   343  	Server:       "machine-0",
   344  	Agent:        "machine-0",
   345  	ConnectionID: 1234,
   346  }
   347  var ha1 = presence.Value{
   348  	Model:        bootstrapUUID,
   349  	Server:       "machine-1",
   350  	Agent:        "machine-1",
   351  	ConnectionID: 1235,
   352  }
   353  var ha2 = presence.Value{
   354  	Model:        bootstrapUUID,
   355  	Server:       "machine-2",
   356  	Agent:        "machine-2",
   357  	ConnectionID: 1236,
   358  }
   359  
   360  var modelMachine0 = presence.Value{
   361  	Model:        modelUUID,
   362  	Server:       "machine-1",
   363  	Agent:        "machine-0",
   364  	ConnectionID: 1237,
   365  }
   366  var modelMachine1 = presence.Value{
   367  	Model:        modelUUID,
   368  	Server:       "machine-2",
   369  	Agent:        "machine-1",
   370  	ConnectionID: 1238,
   371  }
   372  var modelUnit1 = presence.Value{
   373  	Model:        modelUUID,
   374  	Server:       "machine-0",
   375  	Agent:        "unit-wordpress-0",
   376  	ConnectionID: 1239,
   377  }
   378  var modelUnit2 = presence.Value{
   379  	Model:        modelUUID,
   380  	Server:       "machine-0",
   381  	Agent:        "unit-mysql-0",
   382  	ConnectionID: 12409,
   383  }