github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/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  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/names/v5"
    13  	"github.com/juju/pubsub/v2"
    14  	"github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	"github.com/juju/worker/v3"
    17  	"github.com/juju/worker/v3/workertest"
    18  	"github.com/kr/pretty"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	corepresence "github.com/juju/juju/core/presence"
    22  	"github.com/juju/juju/pubsub/apiserver"
    23  	"github.com/juju/juju/pubsub/centralhub"
    24  	"github.com/juju/juju/pubsub/forwarder"
    25  	coretesting "github.com/juju/juju/testing"
    26  	"github.com/juju/juju/worker/presence"
    27  )
    28  
    29  type PresenceSuite struct {
    30  	testing.IsolationSuite
    31  	hub      *pubsub.StructuredHub
    32  	clock    *testclock.Clock
    33  	recorder corepresence.Recorder
    34  	config   presence.WorkerConfig
    35  }
    36  
    37  var _ = gc.Suite(&PresenceSuite{})
    38  
    39  func (s *PresenceSuite) SetUpTest(c *gc.C) {
    40  	s.IsolationSuite.SetUpTest(c)
    41  	s.hub = centralhub.New(ourTag, centralhub.PubsubNoOpMetrics{})
    42  	s.clock = testclock.NewClock(time.Time{})
    43  	s.recorder = corepresence.New(s.clock)
    44  	s.recorder.Enable()
    45  	s.config = presence.WorkerConfig{
    46  		Origin:   ourServer,
    47  		Hub:      s.hub,
    48  		Recorder: s.recorder,
    49  		Logger:   loggo.GetLogger("test"),
    50  	}
    51  	loggo.ConfigureLoggers("<root>=trace")
    52  }
    53  
    54  func (s *PresenceSuite) worker(c *gc.C) worker.Worker {
    55  	w, err := presence.NewWorker(s.config)
    56  	c.Assert(err, jc.ErrorIsNil)
    57  	return w
    58  }
    59  
    60  func (s *PresenceSuite) TestWorkerConfigMissingOrigin(c *gc.C) {
    61  	s.config.Origin = ""
    62  	err := s.config.Validate()
    63  	c.Check(err, jc.Satisfies, errors.IsNotValid)
    64  	c.Check(err, gc.ErrorMatches, "missing origin not valid")
    65  }
    66  
    67  func (s *PresenceSuite) TestWorkerConfigMissingHub(c *gc.C) {
    68  	s.config.Hub = nil
    69  	err := s.config.Validate()
    70  	c.Check(err, jc.Satisfies, errors.IsNotValid)
    71  	c.Check(err, gc.ErrorMatches, "missing hub not valid")
    72  }
    73  
    74  func (s *PresenceSuite) TestWorkerConfigMissingRecorder(c *gc.C) {
    75  	s.config.Recorder = nil
    76  	err := s.config.Validate()
    77  	c.Check(err, jc.Satisfies, errors.IsNotValid)
    78  	c.Check(err, gc.ErrorMatches, "missing recorder not valid")
    79  }
    80  
    81  func (s *PresenceSuite) TestWorkerConfigMissingLogger(c *gc.C) {
    82  	s.config.Logger = nil
    83  	err := s.config.Validate()
    84  	c.Check(err, jc.Satisfies, errors.IsNotValid)
    85  	c.Check(err, gc.ErrorMatches, "missing logger not valid")
    86  }
    87  
    88  func (s *PresenceSuite) TestNewWorkerValidatesConfig(c *gc.C) {
    89  	w, err := presence.NewWorker(presence.WorkerConfig{})
    90  	c.Check(err, gc.ErrorMatches, "missing origin not valid")
    91  	c.Check(w, gc.IsNil)
    92  }
    93  
    94  func (s *PresenceSuite) TestWorkerDies(c *gc.C) {
    95  	w := s.worker(c)
    96  	workertest.CleanKill(c, w)
    97  }
    98  
    99  func (s *PresenceSuite) TestReport(c *gc.C) {
   100  	w := s.worker(c)
   101  	defer workertest.CleanKill(c, w)
   102  
   103  	s.recorder.Connect("machine-0", "model-uuid", "agent", 1, false, "")
   104  	s.recorder.Connect("machine-0", "model-uuid", "agent", 2, false, "")
   105  	s.recorder.Connect("machine-0", "model-uuid", "agent", 3, false, "")
   106  	s.recorder.Connect("machine-1", "model-uuid", "agent", 4, false, "")
   107  	s.recorder.Connect("machine-1", "model-uuid", "agent", 5, false, "")
   108  	s.recorder.Connect("machine-2", "model-uuid", "agent", 6, false, "")
   109  
   110  	reporter, ok := w.(worker.Reporter)
   111  	c.Assert(ok, jc.IsTrue)
   112  	c.Assert(reporter.Report(), jc.DeepEquals, map[string]interface{}{
   113  		"machine-0": 3,
   114  		"machine-1": 2,
   115  		"machine-2": 1,
   116  	})
   117  }
   118  
   119  func (s *PresenceSuite) TestForwarderConnectToOther(c *gc.C) {
   120  	w := s.worker(c)
   121  	defer workertest.CleanKill(c, w)
   122  
   123  	done := make(chan struct{})
   124  
   125  	unsub, err := s.hub.Subscribe(apiserver.PresenceRequestTopic, func(topic string, data apiserver.OriginTarget, err error) {
   126  		c.Logf("handler called for %q", topic)
   127  		c.Check(err, jc.ErrorIsNil)
   128  		c.Check(data.Target, gc.Equals, otherServer)
   129  		c.Check(data.Origin, gc.Equals, ourServer)
   130  		close(done)
   131  	})
   132  	c.Assert(err, jc.ErrorIsNil)
   133  	defer unsub()
   134  
   135  	// When connections are established from us to them, we ask for their presence info.
   136  	_, err = s.hub.Publish(
   137  		forwarder.ConnectedTopic,
   138  		apiserver.OriginTarget{Origin: ourServer, Target: otherServer})
   139  	c.Assert(err, jc.ErrorIsNil)
   140  	s.AssertDone(c, done)
   141  }
   142  
   143  func (s *PresenceSuite) TestForwarderConnectFromOther(c *gc.C) {
   144  	w := s.worker(c)
   145  	defer workertest.CleanKill(c, w)
   146  
   147  	done := make(chan struct{})
   148  
   149  	unsub, err := s.hub.Subscribe(apiserver.PresenceRequestTopic, func(topic string, data apiserver.OriginTarget, err error) {
   150  		c.Logf("handler called for %q", topic)
   151  		c.Check(err, jc.ErrorIsNil)
   152  		c.Check(data.Target, gc.Equals, otherServer)
   153  		c.Check(data.Origin, gc.Equals, ourServer)
   154  		close(done)
   155  	})
   156  	c.Assert(err, jc.ErrorIsNil)
   157  	defer unsub()
   158  
   159  	// When connections are established from them to us, we ask for their presence info.
   160  	_, err = s.hub.Publish(
   161  		forwarder.ConnectedTopic,
   162  		apiserver.OriginTarget{Origin: otherServer, Target: ourServer})
   163  	c.Assert(err, jc.ErrorIsNil)
   164  	s.AssertDone(c, done)
   165  }
   166  
   167  func (s *PresenceSuite) TestForwarderConnectOtherIgnored(c *gc.C) {
   168  	w := s.worker(c)
   169  	defer workertest.CleanKill(c, w)
   170  
   171  	called := make(chan struct{})
   172  
   173  	unsub, err := s.hub.Subscribe(apiserver.PresenceRequestTopic, func(topic string, data apiserver.OriginTarget, err error) {
   174  		c.Logf("handler called for %q", topic)
   175  		close(called)
   176  	})
   177  	c.Assert(err, jc.ErrorIsNil)
   178  	defer unsub()
   179  
   180  	_, err = s.hub.Publish(
   181  		forwarder.ConnectedTopic,
   182  		apiserver.OriginTarget{Origin: otherServer, Target: "machine-8"})
   183  	c.Assert(err, jc.ErrorIsNil)
   184  	s.AssertNotCalled(c, called)
   185  }
   186  
   187  func (s *PresenceSuite) TestForwarderDisconnectConnectFromOther(c *gc.C) {
   188  	w := s.worker(c)
   189  	defer workertest.CleanKill(c, w)
   190  
   191  	connect(s.recorder, agent1, agent2)
   192  
   193  	done, err := s.hub.Publish(
   194  		forwarder.DisconnectedTopic,
   195  		apiserver.OriginTarget{Origin: ourServer, Target: otherServer})
   196  	c.Assert(err, jc.ErrorIsNil)
   197  	s.AssertDone(c, pubsub.Wait(done))
   198  	s.AssertConnections(c, alive(agent1), missing(agent2))
   199  }
   200  
   201  func (s *PresenceSuite) TestForwarderDisconnectOthersIgnored(c *gc.C) {
   202  	w := s.worker(c)
   203  	defer workertest.CleanKill(c, w)
   204  
   205  	connect(s.recorder, agent1, agent2)
   206  
   207  	done, err := s.hub.Publish(
   208  		forwarder.DisconnectedTopic,
   209  		apiserver.OriginTarget{Origin: "machine-7", Target: otherServer})
   210  	c.Assert(err, jc.ErrorIsNil)
   211  	s.AssertDone(c, pubsub.Wait(done))
   212  	s.AssertConnections(c, alive(agent1), alive(agent2))
   213  }
   214  
   215  func (s *PresenceSuite) TestConnectTopic(c *gc.C) {
   216  	w := s.worker(c)
   217  	defer workertest.CleanKill(c, w)
   218  
   219  	done, err := s.hub.Publish(
   220  		apiserver.ConnectTopic,
   221  		apiserver.APIConnection{
   222  			Origin:          "machine-5",
   223  			ModelUUID:       "model-uuid",
   224  			AgentTag:        "agent-2",
   225  			ConnectionID:    42,
   226  			ControllerAgent: true,
   227  			UserData:        "test",
   228  		})
   229  	c.Assert(err, jc.ErrorIsNil)
   230  	s.AssertDone(c, pubsub.Wait(done))
   231  	s.AssertConnections(c, corepresence.Value{
   232  		Model:           "model-uuid",
   233  		Server:          "machine-5",
   234  		Agent:           "agent-2",
   235  		ConnectionID:    42,
   236  		Status:          corepresence.Alive,
   237  		ControllerAgent: true,
   238  		UserData:        "test",
   239  	})
   240  }
   241  
   242  func (s *PresenceSuite) TestDisconnectTopic(c *gc.C) {
   243  	w := s.worker(c)
   244  	defer workertest.CleanKill(c, w)
   245  
   246  	connect(s.recorder, agent1, agent2)
   247  
   248  	done, err := s.hub.Publish(
   249  		apiserver.DisconnectTopic,
   250  		apiserver.APIConnection{
   251  			Origin:       agent2.Server,
   252  			ConnectionID: agent2.ConnectionID,
   253  		})
   254  	c.Assert(err, jc.ErrorIsNil)
   255  	s.AssertDone(c, pubsub.Wait(done))
   256  	s.AssertConnections(c, alive(agent1))
   257  }
   258  
   259  func (s *PresenceSuite) TestPresenceRequest(c *gc.C) {
   260  	w := s.worker(c)
   261  	defer workertest.CleanKill(c, w)
   262  
   263  	connect(s.recorder, agent1, agent2, agent3, agent4)
   264  
   265  	done := make(chan struct{})
   266  	unsub, err := s.hub.Subscribe(apiserver.PresenceResponseTopic, func(topic string, data apiserver.PresenceResponse, err error) {
   267  		c.Logf("handler called for %q", topic)
   268  		c.Check(err, jc.ErrorIsNil)
   269  		c.Check(data.Origin, gc.Equals, ourServer)
   270  
   271  		c.Check(data.Connections, gc.HasLen, 2)
   272  		s.CheckConnection(c, data.Connections[0], agent1)
   273  		s.CheckConnection(c, data.Connections[1], agent3)
   274  
   275  		close(done)
   276  	})
   277  	c.Assert(err, jc.ErrorIsNil)
   278  	defer unsub()
   279  
   280  	// When asked for our presence, we respond with the agents connected to us.
   281  	_, err = s.hub.Publish(
   282  		apiserver.PresenceRequestTopic,
   283  		apiserver.OriginTarget{Origin: otherServer, Target: ourServer})
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	s.AssertDone(c, done)
   286  }
   287  
   288  func (s *PresenceSuite) TestPresenceRequestOtherServer(c *gc.C) {
   289  	w := s.worker(c)
   290  	defer workertest.CleanKill(c, w)
   291  
   292  	called := make(chan struct{})
   293  	unsub, err := s.hub.Subscribe(apiserver.PresenceResponseTopic, func(topic string, data apiserver.PresenceResponse, err error) {
   294  		c.Logf("handler called for %q", topic)
   295  		close(called)
   296  	})
   297  	c.Assert(err, jc.ErrorIsNil)
   298  	defer unsub()
   299  
   300  	// When presence requests come in for other servers, we ignore them.
   301  	_, err = s.hub.Publish(
   302  		apiserver.PresenceRequestTopic,
   303  		apiserver.OriginTarget{Origin: otherServer, Target: "another"})
   304  	c.Assert(err, jc.ErrorIsNil)
   305  	s.AssertNotCalled(c, called)
   306  }
   307  
   308  func (s *PresenceSuite) TestPresenceResponse(c *gc.C) {
   309  	w := s.worker(c)
   310  	defer workertest.CleanKill(c, w)
   311  
   312  	connect(s.recorder, agent1, agent2, agent3, agent4)
   313  	s.recorder.ServerDown(otherServer)
   314  
   315  	// When connections information comes from other servers, we update our recorder.
   316  	done, err := s.hub.Publish(
   317  		apiserver.PresenceResponseTopic,
   318  		apiserver.PresenceResponse{
   319  			Origin: otherServer,
   320  			Connections: []apiserver.APIConnection{
   321  				apiConn(agent2), apiConn(agent4),
   322  			},
   323  		})
   324  	c.Assert(err, jc.ErrorIsNil)
   325  	s.AssertDone(c, pubsub.Wait(done))
   326  
   327  	s.AssertConnections(c, alive(agent1), alive(agent2), alive(agent3), alive(agent4))
   328  }
   329  
   330  func (s *PresenceSuite) AssertDone(c *gc.C, called <-chan struct{}) {
   331  	select {
   332  	case <-called:
   333  	case <-time.After(coretesting.LongWait):
   334  		c.Fatal("event not handled")
   335  	}
   336  }
   337  
   338  func (s *PresenceSuite) AssertNotCalled(c *gc.C, called <-chan struct{}) {
   339  	select {
   340  	case <-called:
   341  		c.Fatal("event called unexpectedly")
   342  	case <-time.After(coretesting.ShortWait):
   343  	}
   344  }
   345  
   346  func (s *PresenceSuite) AssertConnections(c *gc.C, values ...corepresence.Value) {
   347  	connections := s.recorder.Connections()
   348  	c.Log(pretty.Sprint(connections))
   349  	c.Assert(connections.Values(), jc.SameContents, values)
   350  }
   351  
   352  func (s *PresenceSuite) CheckConnection(c *gc.C, conn apiserver.APIConnection, agent corepresence.Value) {
   353  	c.Check(conn.AgentTag, gc.Equals, agent.Agent)
   354  	c.Check(conn.ControllerAgent, gc.Equals, agent.ControllerAgent)
   355  	c.Check(conn.ModelUUID, gc.Equals, agent.Model)
   356  	c.Check(conn.ConnectionID, gc.Equals, agent.ConnectionID)
   357  	c.Check(conn.Origin, gc.Equals, agent.Server)
   358  	c.Check(conn.UserData, gc.Equals, agent.UserData)
   359  }
   360  
   361  func apiConn(value corepresence.Value) apiserver.APIConnection {
   362  	return apiserver.APIConnection{
   363  		AgentTag:        value.Agent,
   364  		ControllerAgent: value.ControllerAgent,
   365  		ModelUUID:       value.Model,
   366  		ConnectionID:    value.ConnectionID,
   367  		Origin:          value.Server,
   368  		UserData:        value.UserData,
   369  	}
   370  }
   371  
   372  func alive(v corepresence.Value) corepresence.Value {
   373  	v.Status = corepresence.Alive
   374  	return v
   375  }
   376  
   377  func missing(v corepresence.Value) corepresence.Value {
   378  	v.Status = corepresence.Missing
   379  	return v
   380  }
   381  
   382  func connect(r corepresence.Recorder, values ...corepresence.Value) {
   383  	for _, info := range values {
   384  		r.Connect(info.Server, info.Model, info.Agent, info.ConnectionID, info.ControllerAgent, info.UserData)
   385  	}
   386  }
   387  
   388  const modelUUID = "model-uuid"
   389  
   390  var (
   391  	ourTag      = names.NewMachineTag("1")
   392  	ourServer   = ourTag.String()
   393  	otherServer = "machine-2"
   394  	agent1      = corepresence.Value{
   395  		Model:        modelUUID,
   396  		Server:       ourServer,
   397  		Agent:        "machine-0",
   398  		ConnectionID: 1237,
   399  		UserData:     "foo",
   400  	}
   401  	agent2 = corepresence.Value{
   402  		Model:        modelUUID,
   403  		Server:       otherServer,
   404  		Agent:        "machine-1",
   405  		ConnectionID: 1238,
   406  		UserData:     "bar",
   407  	}
   408  	agent3 = corepresence.Value{
   409  		Model:        modelUUID,
   410  		Server:       ourServer,
   411  		Agent:        "unit-ubuntu-0",
   412  		ConnectionID: 1239,
   413  		UserData:     "baz",
   414  	}
   415  	agent4 = corepresence.Value{
   416  		Model:        modelUUID,
   417  		Server:       otherServer,
   418  		Agent:        "unit-ubuntu-1",
   419  		ConnectionID: 1240,
   420  		UserData:     "splat",
   421  	}
   422  )