github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/pubsub/subscriber_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package pubsub_test
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/names/v5"
    14  	"github.com/juju/pubsub/v2"
    15  	"github.com/juju/testing"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/worker/v3"
    18  	"github.com/juju/worker/v3/workertest"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	"github.com/juju/juju/api"
    22  	"github.com/juju/juju/pubsub/apiserver"
    23  	"github.com/juju/juju/pubsub/centralhub"
    24  	"github.com/juju/juju/rpc/params"
    25  	coretesting "github.com/juju/juju/testing"
    26  	psworker "github.com/juju/juju/worker/pubsub"
    27  )
    28  
    29  type WorkerConfigSuite struct {
    30  }
    31  
    32  var _ = gc.Suite(&WorkerConfigSuite{})
    33  
    34  func (*WorkerConfigSuite) TestValidate(c *gc.C) {
    35  	logger := loggo.GetLogger("juju.worker.pubsub")
    36  	for i, test := range []struct {
    37  		cfg      psworker.WorkerConfig
    38  		errMatch string
    39  	}{
    40  		{
    41  			errMatch: "missing origin not valid",
    42  		}, {
    43  			cfg: psworker.WorkerConfig{
    44  				Origin: "origin",
    45  			},
    46  			errMatch: "missing clock not valid",
    47  		}, {
    48  			cfg: psworker.WorkerConfig{
    49  				Origin: "origin",
    50  				Clock:  testclock.NewClock(time.Now()),
    51  			},
    52  			errMatch: "missing hub not valid",
    53  		}, {
    54  			cfg: psworker.WorkerConfig{
    55  				Origin: "origin",
    56  				Clock:  testclock.NewClock(time.Now()),
    57  				Hub:    pubsub.NewStructuredHub(nil),
    58  			},
    59  			errMatch: "missing logger not valid",
    60  		}, {
    61  			cfg: psworker.WorkerConfig{
    62  				Origin: "origin",
    63  				Clock:  testclock.NewClock(time.Now()),
    64  				Hub:    pubsub.NewStructuredHub(nil),
    65  				Logger: logger,
    66  			},
    67  			errMatch: "missing api info not valid",
    68  		}, {
    69  			cfg: psworker.WorkerConfig{
    70  				Origin: "origin",
    71  				Clock:  testclock.NewClock(time.Now()),
    72  				Hub:    pubsub.NewStructuredHub(nil),
    73  				Logger: logger,
    74  				APIInfo: &api.Info{
    75  					Addrs: []string{"localhost"},
    76  				},
    77  			},
    78  			errMatch: "missing new writer not valid",
    79  		}, {
    80  			cfg: psworker.WorkerConfig{
    81  				Origin: "origin",
    82  				Clock:  testclock.NewClock(time.Now()),
    83  				Hub:    pubsub.NewStructuredHub(nil),
    84  				Logger: logger,
    85  				APIInfo: &api.Info{
    86  					Addrs: []string{"localhost"},
    87  				},
    88  				NewWriter: func(*api.Info) (psworker.MessageWriter, error) {
    89  					return &messageWriter{}, nil
    90  				},
    91  			},
    92  			errMatch: "missing new remote not valid",
    93  		}, {
    94  			cfg: psworker.WorkerConfig{
    95  				Origin: "origin",
    96  				Clock:  testclock.NewClock(time.Now()),
    97  				Hub:    pubsub.NewStructuredHub(nil),
    98  				Logger: logger,
    99  				APIInfo: &api.Info{
   100  					Addrs: []string{"localhost"},
   101  				},
   102  				NewWriter: func(*api.Info) (psworker.MessageWriter, error) {
   103  					return &messageWriter{}, nil
   104  				},
   105  				NewRemote: func(psworker.RemoteServerConfig) (psworker.RemoteServer, error) {
   106  					return &fakeRemote{}, nil
   107  				},
   108  			},
   109  		},
   110  	} {
   111  		c.Logf("test %d", i)
   112  		err := test.cfg.Validate()
   113  		if test.errMatch != "" {
   114  			c.Check(err, gc.ErrorMatches, test.errMatch)
   115  			c.Check(err, jc.Satisfies, errors.IsNotValid)
   116  		} else {
   117  			c.Check(err, jc.ErrorIsNil)
   118  		}
   119  	}
   120  }
   121  
   122  type SubscriberSuite struct {
   123  	testing.IsolationSuite
   124  	config  psworker.WorkerConfig
   125  	clock   *testclock.Clock
   126  	hub     *pubsub.StructuredHub
   127  	origin  string
   128  	remotes *fakeRemoteTracker
   129  }
   130  
   131  var _ = gc.Suite(&SubscriberSuite{})
   132  
   133  func (s *SubscriberSuite) SetUpTest(c *gc.C) {
   134  	s.IsolationSuite.SetUpTest(c)
   135  	logger := loggo.GetLogger("juju.worker.pubsub")
   136  	logger.SetLogLevel(loggo.TRACE)
   137  	// loggo.GetLogger("pubsub").SetLogLevel(loggo.TRACE)
   138  	tag := names.NewMachineTag("42")
   139  	s.clock = testclock.NewClock(time.Now())
   140  	s.hub = centralhub.New(tag, centralhub.PubsubNoOpMetrics{})
   141  	s.origin = tag.String()
   142  	s.remotes = &fakeRemoteTracker{
   143  		remotes: make(map[string]*fakeRemote),
   144  	}
   145  	s.config = psworker.WorkerConfig{
   146  		Origin: s.origin,
   147  		Clock:  s.clock,
   148  		Hub:    s.hub,
   149  		Logger: logger,
   150  		APIInfo: &api.Info{
   151  			Addrs:  []string{"localhost"},
   152  			CACert: "fake as",
   153  			Tag:    tag,
   154  		},
   155  		NewWriter: func(*api.Info) (psworker.MessageWriter, error) {
   156  			return &messageWriter{}, nil
   157  		},
   158  		NewRemote: s.remotes.new,
   159  	}
   160  }
   161  
   162  func (s *SubscriberSuite) TestBadConfig(c *gc.C) {
   163  	s.config.Clock = nil
   164  	w, err := psworker.NewWorker(s.config)
   165  	c.Assert(err, gc.ErrorMatches, "missing clock not valid")
   166  	c.Assert(w, gc.IsNil)
   167  }
   168  
   169  func (s *SubscriberSuite) TestCleanShutdown(c *gc.C) {
   170  	w, err := psworker.NewWorker(s.config)
   171  	c.Assert(err, jc.ErrorIsNil)
   172  	workertest.CleanKill(c, w)
   173  }
   174  
   175  func (s *SubscriberSuite) TestNoInitialRemotes(c *gc.C) {
   176  	w, err := psworker.NewWorker(s.config)
   177  	c.Assert(err, jc.ErrorIsNil)
   178  	defer workertest.CleanKill(c, w)
   179  
   180  	c.Assert(s.remotes.remotes, gc.HasLen, 0)
   181  }
   182  
   183  func (s *SubscriberSuite) enableHA(c *gc.C) {
   184  	done, err := s.hub.Publish(apiserver.DetailsTopic, apiserver.Details{
   185  		Servers: map[string]apiserver.APIServer{
   186  			"3": {
   187  				ID:        "3",
   188  				Addresses: []string{"10.1.2.3"},
   189  			},
   190  			"5": {
   191  				ID:        "5",
   192  				Addresses: []string{"10.1.2.5"},
   193  			},
   194  			"42": {
   195  				ID:        "42",
   196  				Addresses: []string{"10.1.2.42"},
   197  			},
   198  		},
   199  		LocalOnly: true,
   200  	})
   201  	c.Assert(err, jc.ErrorIsNil)
   202  
   203  	select {
   204  	case <-pubsub.Wait(done):
   205  	case <-time.After(coretesting.LongWait):
   206  		c.Fatal("message handling not completed")
   207  	}
   208  }
   209  
   210  func (s *SubscriberSuite) newHAWorker(c *gc.C) worker.Worker {
   211  	w, err := psworker.NewWorker(s.config)
   212  	c.Assert(err, jc.ErrorIsNil)
   213  	s.AddCleanup(func(c *gc.C) { workertest.CleanKill(c, w) })
   214  	s.enableHA(c)
   215  	return w
   216  }
   217  
   218  func (s *SubscriberSuite) TestEnableHA(c *gc.C) {
   219  	s.newHAWorker(c)
   220  
   221  	c.Assert(s.remotes.remotes, gc.HasLen, 2)
   222  	remote3 := s.remotes.remotes["machine-3"]
   223  	c.Assert(remote3.config.APIInfo.Addrs, jc.DeepEquals, []string{"10.1.2.3"})
   224  	remote5 := s.remotes.remotes["machine-5"]
   225  	c.Assert(remote5.config.APIInfo.Addrs, jc.DeepEquals, []string{"10.1.2.5"})
   226  }
   227  
   228  func (s *SubscriberSuite) TestEnableHAInternalAddress(c *gc.C) {
   229  	w, err := psworker.NewWorker(s.config)
   230  	c.Assert(err, jc.ErrorIsNil)
   231  	s.AddCleanup(func(c *gc.C) { workertest.CleanKill(c, w) })
   232  	done, err := s.hub.Publish(apiserver.DetailsTopic, apiserver.Details{
   233  		Servers: map[string]apiserver.APIServer{
   234  			"3": {
   235  				ID:              "3",
   236  				Addresses:       []string{"10.1.2.3"},
   237  				InternalAddress: "10.5.4.3",
   238  			},
   239  			"5": {
   240  				ID:              "5",
   241  				Addresses:       []string{"10.1.2.5"},
   242  				InternalAddress: "10.5.4.4",
   243  			},
   244  			"42": {
   245  				ID:              "42",
   246  				Addresses:       []string{"10.1.2.42"},
   247  				InternalAddress: "10.5.4.5",
   248  			},
   249  		},
   250  		LocalOnly: true,
   251  	})
   252  	c.Assert(err, jc.ErrorIsNil)
   253  
   254  	select {
   255  	case <-pubsub.Wait(done):
   256  	case <-time.After(coretesting.LongWait):
   257  		c.Fatal("message handling not completed")
   258  	}
   259  	c.Assert(s.remotes.remotes, gc.HasLen, 2)
   260  	remote3 := s.remotes.remotes["machine-3"]
   261  	c.Assert(remote3.config.APIInfo.Addrs, jc.DeepEquals, []string{"10.5.4.3"})
   262  	remote5 := s.remotes.remotes["machine-5"]
   263  	c.Assert(remote5.config.APIInfo.Addrs, jc.DeepEquals, []string{"10.5.4.4"})
   264  }
   265  
   266  func (s *SubscriberSuite) TestSameMessagesForwardedForMachine(c *gc.C) {
   267  	s.newHAWorker(c)
   268  
   269  	var expected []*params.PubSubMessage
   270  	var last <-chan struct{}
   271  	for i := 0; i < 10; i++ {
   272  		message := &params.PubSubMessage{
   273  			Topic: fmt.Sprintf("topic.%d", i),
   274  			Data:  map[string]interface{}{"origin": "machine-42"},
   275  		}
   276  		expected = append(expected, message)
   277  		done, err := s.hub.Publish(message.Topic, nil)
   278  		c.Assert(err, jc.ErrorIsNil)
   279  		last = pubsub.Wait(done)
   280  	}
   281  	select {
   282  	case <-last:
   283  		c.Logf("message processing complete")
   284  	case <-time.After(coretesting.LongWait):
   285  		c.Fatal("messages not handled")
   286  	}
   287  
   288  	c.Assert(s.remotes.remotes, gc.HasLen, 2)
   289  	remote3 := s.remotes.remotes["machine-3"]
   290  	remote5 := s.remotes.remotes["machine-5"]
   291  
   292  	c.Assert(remote3.messages, jc.DeepEquals, expected)
   293  	c.Assert(remote5.messages, jc.DeepEquals, expected)
   294  }
   295  
   296  func (s *SubscriberSuite) TestSameMessagesForwardedForController(c *gc.C) {
   297  	tag := names.NewControllerAgentTag("42")
   298  	s.origin = tag.String()
   299  	s.hub = centralhub.New(tag, centralhub.PubsubNoOpMetrics{})
   300  	s.config.Origin = s.origin
   301  	s.config.Hub = s.hub
   302  	s.config.APIInfo.Tag = tag
   303  
   304  	s.newHAWorker(c)
   305  
   306  	var expected []*params.PubSubMessage
   307  	var last <-chan struct{}
   308  	for i := 0; i < 10; i++ {
   309  		message := &params.PubSubMessage{
   310  			Topic: fmt.Sprintf("topic.%d", i),
   311  			Data:  map[string]interface{}{"origin": "controller-42"},
   312  		}
   313  		expected = append(expected, message)
   314  		done, err := s.hub.Publish(message.Topic, nil)
   315  		c.Assert(err, jc.ErrorIsNil)
   316  		last = pubsub.Wait(done)
   317  	}
   318  	select {
   319  	case <-last:
   320  		c.Logf("message processing complete")
   321  	case <-time.After(coretesting.LongWait):
   322  		c.Fatal("messages not handled")
   323  	}
   324  
   325  	c.Assert(s.remotes.remotes, gc.HasLen, 2)
   326  	remote3 := s.remotes.remotes["controller-3"]
   327  	remote5 := s.remotes.remotes["controller-5"]
   328  
   329  	c.Assert(remote3.messages, jc.DeepEquals, expected)
   330  	c.Assert(remote5.messages, jc.DeepEquals, expected)
   331  }
   332  
   333  func (s *SubscriberSuite) TestLocalMessagesNotForwarded(c *gc.C) {
   334  	s.newHAWorker(c)
   335  
   336  	var last <-chan struct{}
   337  	for i := 0; i < 10; i++ {
   338  		done, err := s.hub.Publish("local.message", map[string]interface{}{
   339  			"foo":        "bar",
   340  			"local-only": true,
   341  		})
   342  		c.Assert(err, jc.ErrorIsNil)
   343  		last = pubsub.Wait(done)
   344  	}
   345  	select {
   346  	case <-last:
   347  		c.Logf("message processing complete")
   348  	case <-time.After(coretesting.LongWait):
   349  		c.Fatal("messages not handled")
   350  	}
   351  
   352  	c.Assert(s.remotes.remotes, gc.HasLen, 2)
   353  	remote3 := s.remotes.remotes["machine-3"]
   354  	remote5 := s.remotes.remotes["machine-5"]
   355  
   356  	c.Assert(remote3.messages, gc.HasLen, 0)
   357  	c.Assert(remote5.messages, gc.HasLen, 0)
   358  }
   359  
   360  func (s *SubscriberSuite) TestOtherOriginMessagesNotForwarded(c *gc.C) {
   361  	s.newHAWorker(c)
   362  
   363  	var last <-chan struct{}
   364  	for i := 0; i < 10; i++ {
   365  		done, err := s.hub.Publish("not.ours", map[string]interface{}{
   366  			"foo":    "bar",
   367  			"origin": "other",
   368  		})
   369  		c.Assert(err, jc.ErrorIsNil)
   370  		last = pubsub.Wait(done)
   371  	}
   372  	select {
   373  	case <-last:
   374  		c.Logf("message processing complete")
   375  	case <-time.After(coretesting.LongWait):
   376  		c.Fatal("messages not handled")
   377  	}
   378  
   379  	c.Assert(s.remotes.remotes, gc.HasLen, 2)
   380  	remote3 := s.remotes.remotes["machine-3"]
   381  	remote5 := s.remotes.remotes["machine-5"]
   382  
   383  	c.Assert(remote3.messages, gc.HasLen, 0)
   384  	c.Assert(remote5.messages, gc.HasLen, 0)
   385  }
   386  
   387  func (s *SubscriberSuite) TestIntrospectionReport(c *gc.C) {
   388  	w := s.newHAWorker(c)
   389  
   390  	r, ok := w.(psworker.Reporter)
   391  	c.Assert(ok, jc.IsTrue)
   392  	c.Assert(r.IntrospectionReport(), gc.Equals, ""+
   393  		"Source: machine-42\n"+
   394  		"\n"+
   395  		"Target: machine-3\n"+
   396  		"  Status: connected\n"+
   397  		"  Addresses: [10.1.2.3]\n"+
   398  		"\n"+
   399  		"Target: machine-5\n"+
   400  		"  Status: connected\n"+
   401  		"  Addresses: [10.1.2.5]\n")
   402  }
   403  
   404  func (s *SubscriberSuite) TestReport(c *gc.C) {
   405  	w := s.newHAWorker(c)
   406  
   407  	r, ok := w.(psworker.Reporter)
   408  	c.Assert(ok, jc.IsTrue)
   409  	c.Assert(r.Report(), jc.DeepEquals, map[string]interface{}{
   410  		"source": "machine-42",
   411  		"targets": map[string]interface{}{
   412  			"machine-3": map[string]interface{}{
   413  				"status":    "connected",
   414  				"addresses": []string{"10.1.2.3"},
   415  			},
   416  			"machine-5": map[string]interface{}{
   417  				"status":    "connected",
   418  				"addresses": []string{"10.1.2.5"},
   419  			},
   420  		}})
   421  }
   422  
   423  func (s *SubscriberSuite) TestRequestsDetailsOnceSubscribed(c *gc.C) {
   424  	subscribed := make(chan apiserver.DetailsRequest)
   425  	s.config.Hub.Subscribe(apiserver.DetailsRequestTopic,
   426  		func(_ string, req apiserver.DetailsRequest, err error) {
   427  			c.Check(err, jc.ErrorIsNil)
   428  			subscribed <- req
   429  		},
   430  	)
   431  
   432  	s.newHAWorker(c)
   433  
   434  	select {
   435  	case req := <-subscribed:
   436  		c.Assert(req, gc.Equals, apiserver.DetailsRequest{Requester: "pubsub-forwarder", LocalOnly: true})
   437  	case <-time.After(coretesting.LongWait):
   438  		c.Fatalf("timed out waiting for details request")
   439  	}
   440  }
   441  
   442  var logger = loggo.GetLogger("workertest")
   443  
   444  type fakeRemoteTracker struct {
   445  	remotes map[string]*fakeRemote
   446  }
   447  
   448  func (f *fakeRemoteTracker) new(config psworker.RemoteServerConfig) (psworker.RemoteServer, error) {
   449  	remote := &fakeRemote{config: config}
   450  	f.remotes[config.Target] = remote
   451  	return remote, nil
   452  }
   453  
   454  type fakeRemote struct {
   455  	psworker.RemoteServer
   456  	config   psworker.RemoteServerConfig
   457  	messages []*params.PubSubMessage
   458  }
   459  
   460  func (f *fakeRemote) Report() map[string]interface{} {
   461  	return map[string]interface{}{
   462  		"status":    "connected",
   463  		"addresses": f.config.APIInfo.Addrs,
   464  	}
   465  }
   466  
   467  func (f *fakeRemote) IntrospectionReport() string {
   468  	return fmt.Sprintf(""+
   469  		"  Status: connected\n"+
   470  		"  Addresses: %v\n",
   471  		f.config.APIInfo.Addrs)
   472  }
   473  
   474  func (f *fakeRemote) Publish(message *params.PubSubMessage) {
   475  	logger.Debugf("fakeRemote.Publish %s to %s", message.Topic, f.config.Target)
   476  	f.messages = append(f.messages, message)
   477  }
   478  func (f *fakeRemote) UpdateAddresses(addresses []string) {
   479  	f.config.APIInfo.Addrs = addresses
   480  }
   481  func (*fakeRemote) Kill()       {}
   482  func (*fakeRemote) Wait() error { return nil }