github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/pubsub/remoteserver_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  	"sync"
     9  	"time"
    10  
    11  	"github.com/juju/clock/testclock"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/pubsub"
    15  	"github.com/juju/testing"
    16  	jc "github.com/juju/testing/checkers"
    17  	gc "gopkg.in/check.v1"
    18  	"gopkg.in/juju/names.v2"
    19  	"gopkg.in/juju/worker.v1/workertest"
    20  
    21  	"github.com/juju/juju/api"
    22  	"github.com/juju/juju/apiserver/params"
    23  	"github.com/juju/juju/pubsub/centralhub"
    24  	"github.com/juju/juju/pubsub/forwarder"
    25  	coretesting "github.com/juju/juju/testing"
    26  	psworker "github.com/juju/juju/worker/pubsub"
    27  )
    28  
    29  type RemoteServerSuite struct {
    30  	testing.IsolationSuite
    31  	connectionOpener *fakeConnectionOpener
    32  	config           psworker.RemoteServerConfig
    33  	clock            *testclock.Clock
    34  	hub              *pubsub.StructuredHub
    35  	origin           string
    36  }
    37  
    38  var _ = gc.Suite(&RemoteServerSuite{})
    39  
    40  func (s *RemoteServerSuite) SetUpTest(c *gc.C) {
    41  	s.IsolationSuite.SetUpTest(c)
    42  	logger := loggo.GetLogger("juju.worker.pubsub")
    43  	logger.SetLogLevel(loggo.TRACE)
    44  	s.connectionOpener = &fakeConnectionOpener{}
    45  	tag := names.NewMachineTag("42")
    46  	s.clock = testclock.NewClock(time.Now())
    47  	s.hub = centralhub.New(tag)
    48  	s.origin = tag.String()
    49  	s.config = psworker.RemoteServerConfig{
    50  		Hub:    s.hub,
    51  		Origin: s.origin,
    52  		Target: "target",
    53  		Clock:  s.clock,
    54  		Logger: logger,
    55  		APIInfo: &api.Info{
    56  			Addrs:  []string{"localhost"},
    57  			CACert: "fake as",
    58  			Tag:    tag,
    59  		},
    60  		NewWriter: s.connectionOpener.newWriter,
    61  	}
    62  }
    63  
    64  func (s *RemoteServerSuite) TestCleanShutdown(c *gc.C) {
    65  	server, err := psworker.NewRemoteServer(s.config)
    66  	c.Assert(err, jc.ErrorIsNil)
    67  	workertest.CleanKill(c, server)
    68  }
    69  
    70  func (s *RemoteServerSuite) TestConnectPublished(c *gc.C) {
    71  	done := make(chan struct{})
    72  	unsub, err := s.config.Hub.Subscribe(forwarder.ConnectedTopic, func(_ string, data map[string]interface{}) {
    73  		c.Check(data["target"], gc.Equals, "target")
    74  		c.Check(data["origin"], gc.Equals, "machine-42")
    75  		close(done)
    76  	})
    77  	c.Assert(err, jc.ErrorIsNil)
    78  	defer unsub()
    79  	server, err := psworker.NewRemoteServer(s.config)
    80  	c.Assert(err, jc.ErrorIsNil)
    81  	defer workertest.CleanKill(c, server)
    82  
    83  	select {
    84  	case <-done:
    85  	case <-time.After(coretesting.LongWait):
    86  		c.Fatal("no connect message published")
    87  	}
    88  	// Make sure that it is reported as started.
    89  	r, ok := server.(psworker.Reporter)
    90  	c.Assert(ok, jc.IsTrue)
    91  	// Since we are just testing the remote, the code that makes sure the
    92  	// published message is forwarded is the subscriber, so we will always
    93  	// show empty queue and none sent.
    94  	c.Check(r.IntrospectionReport(), gc.Equals, ""+
    95  		"  Status: connected\n"+
    96  		"  Addresses: [localhost]\n"+
    97  		"  Queue length: 0\n"+
    98  		"  Sent count: 0\n")
    99  	c.Check(r.Report(), jc.DeepEquals, map[string]interface{}{
   100  		"status":    "connected",
   101  		"addresses": []string{"localhost"},
   102  		"queue-len": 0,
   103  		"sent":      uint64(0),
   104  	})
   105  }
   106  
   107  func (s *RemoteServerSuite) TestDisconnectPublishedOnWriteError(c *gc.C) {
   108  	done := make(chan struct{})
   109  	unsub, err := s.config.Hub.Subscribe(forwarder.DisconnectedTopic, func(_ string, data map[string]interface{}) {
   110  		c.Check(data["target"], gc.Equals, "target")
   111  		c.Check(data["origin"], gc.Equals, "machine-42")
   112  		select {
   113  		case <-done:
   114  			c.Fatal("closed already")
   115  		default:
   116  			close(done)
   117  		}
   118  	})
   119  	c.Assert(err, jc.ErrorIsNil)
   120  	defer unsub()
   121  	s.connectionOpener.forwardErr = errors.New("forward fail")
   122  
   123  	server := s.newConnectedServer(c)
   124  	server.Publish(&params.PubSubMessage{
   125  		Topic: "some topic",
   126  	})
   127  
   128  	select {
   129  	case <-done:
   130  	case <-time.After(coretesting.LongWait):
   131  		c.Fatal("no disconnect message published")
   132  	}
   133  }
   134  
   135  func (s *RemoteServerSuite) TestConnectErrorRetryDelay(c *gc.C) {
   136  	now := s.clock.Now()
   137  	delays := make([]string, 0)
   138  	s.connectionOpener.err = errors.New("oops")
   139  	s.connectionOpener.callback = func(_ *api.Info) {
   140  		delay := s.clock.Now().Sub(now)
   141  		now = s.clock.Now()
   142  		delays = append(delays, fmt.Sprint(delay))
   143  	}
   144  
   145  	server, err := psworker.NewRemoteServer(s.config)
   146  	c.Assert(err, jc.ErrorIsNil)
   147  	defer workertest.CleanKill(c, server)
   148  
   149  	for i := 0; i < 1200; i++ {
   150  		s.clock.WaitAdvance(time.Second, coretesting.ShortWait, 1)
   151  	}
   152  	// Starts immediately, with a one second delay doubling each failure
   153  	// up to a max wait time of 5 minutes.
   154  	c.Assert(delays, jc.DeepEquals, []string{
   155  		"0s", "1s", "2s", "4s", "8s", "16s", "32s",
   156  		"1m4s", "2m8s", "4m16s",
   157  		"5m0s", "5m0s",
   158  	})
   159  }
   160  
   161  func (s *RemoteServerSuite) TestConnectRetryInterruptedOnTargetConnection(c *gc.C) {
   162  	now := s.clock.Now()
   163  	delays := make([]string, 0)
   164  	s.connectionOpener.err = errors.New("oops")
   165  	s.connectionOpener.callback = func(_ *api.Info) {
   166  		delay := s.clock.Now().Sub(now)
   167  		now = s.clock.Now()
   168  		delays = append(delays, fmt.Sprint(delay))
   169  	}
   170  
   171  	server, err := psworker.NewRemoteServer(s.config)
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	defer workertest.CleanKill(c, server)
   174  
   175  	for i := 0; i < 35; i++ {
   176  		s.clock.WaitAdvance(time.Second, coretesting.ShortWait, 1)
   177  	}
   178  	// This leaves us 4s into a 32s retry wait.
   179  	done, err := s.hub.Publish(forwarder.ConnectedTopic, forwarder.OriginTarget{
   180  		Target: s.origin,
   181  		Origin: "target",
   182  	})
   183  	c.Assert(err, jc.ErrorIsNil)
   184  	select {
   185  	case <-done:
   186  	case <-time.After(coretesting.LongWait):
   187  		c.Fatal("worker didn't consume the event")
   188  	}
   189  
   190  	// Now advance the clock some more
   191  	for i := 0; i < 10; i++ {
   192  		s.clock.WaitAdvance(time.Second, coretesting.ShortWait, 2)
   193  	}
   194  
   195  	c.Assert(delays, jc.DeepEquals, []string{
   196  		"0s", "1s", "2s", "4s", "8s", "16s", // standard fallback
   197  		"5s",             // 4s due to interruption, 1s due to loop delay on failure
   198  		"1s", "2s", "4s", // standard fallback
   199  	})
   200  }
   201  
   202  func (s *RemoteServerSuite) TestConnectRetryInterruptedWithNewAddresses(c *gc.C) {
   203  	now := s.clock.Now()
   204  	delays := make([]string, 0)
   205  	expected := []string{"localhost"}
   206  	s.connectionOpener.err = errors.New("oops")
   207  	s.connectionOpener.callback = func(info *api.Info) {
   208  		c.Check(info.Addrs, jc.DeepEquals, expected)
   209  		delay := s.clock.Now().Sub(now)
   210  		now = s.clock.Now()
   211  		delays = append(delays, fmt.Sprint(delay))
   212  	}
   213  
   214  	server, err := psworker.NewRemoteServer(s.config)
   215  	c.Assert(err, jc.ErrorIsNil)
   216  	defer workertest.CleanKill(c, server)
   217  
   218  	for i := 0; i < 35; i++ {
   219  		s.clock.WaitAdvance(time.Second, coretesting.ShortWait, 1)
   220  	}
   221  	// This leaves us 4s into a 32s retry wait.
   222  
   223  	expected = []string{"new addresses"}
   224  	server.UpdateAddresses(expected)
   225  
   226  	// Now advance the clock some more
   227  	for i := 0; i < 10; i++ {
   228  		s.clock.WaitAdvance(time.Second, coretesting.ShortWait, 2)
   229  	}
   230  
   231  	c.Assert(delays, jc.DeepEquals, []string{
   232  		"0s", "1s", "2s", "4s", "8s", "16s", // standard fallback
   233  		"5s",             // 4s due to interruption, 1s due to loop delay on failure
   234  		"1s", "2s", "4s", // standard fallback
   235  	})
   236  }
   237  
   238  func (s *RemoteServerSuite) newConnectedServer(c *gc.C) psworker.RemoteServer {
   239  	connected := make(chan struct{})
   240  	unsub, err := s.config.Hub.Subscribe(forwarder.ConnectedTopic, func(_ string, _ map[string]interface{}) {
   241  		close(connected)
   242  	})
   243  	c.Assert(err, jc.ErrorIsNil)
   244  	defer unsub()
   245  
   246  	server, err := psworker.NewRemoteServer(s.config)
   247  	c.Assert(err, jc.ErrorIsNil)
   248  	s.AddCleanup(func(*gc.C) { workertest.CleanKill(c, server) })
   249  
   250  	select {
   251  	case <-connected:
   252  	case <-time.After(coretesting.LongWait):
   253  		c.Fatal("no connect message published")
   254  	}
   255  	return server
   256  }
   257  
   258  func (s *RemoteServerSuite) TestSendsMessages(c *gc.C) {
   259  	numMessages := 10
   260  	done := make(chan struct{})
   261  	// Close the done channel when the writer has received the
   262  	// appropriate number of messages
   263  	go func() {
   264  		defer close(done)
   265  		for {
   266  			if s.writer().count() == numMessages {
   267  				return
   268  			}
   269  		}
   270  	}()
   271  
   272  	server := s.newConnectedServer(c)
   273  
   274  	for i := 0; i < numMessages; i++ {
   275  		server.Publish(&params.PubSubMessage{
   276  			Topic: fmt.Sprintf("topic.%d", i),
   277  		})
   278  	}
   279  
   280  	select {
   281  	case <-done:
   282  	case <-time.After(coretesting.LongWait):
   283  		c.Fatalf("not all messages received, got %d", s.writer().count())
   284  	}
   285  
   286  	for i := 0; i < numMessages; i++ {
   287  		c.Check(s.writer().messages[i].Topic, gc.Equals, fmt.Sprintf("topic.%d", i))
   288  	}
   289  }
   290  
   291  func (s *RemoteServerSuite) writer() *messageWriter {
   292  	writer := s.connectionOpener.getWriter()
   293  	if writer == nil {
   294  		return &messageWriter{}
   295  	}
   296  	return writer
   297  }
   298  
   299  type fakeConnectionOpener struct {
   300  	mutex      sync.Mutex
   301  	err        error
   302  	callback   func(*api.Info)
   303  	writer     *messageWriter
   304  	forwardErr error
   305  }
   306  
   307  func (f *fakeConnectionOpener) getWriter() *messageWriter {
   308  	f.mutex.Lock()
   309  	defer f.mutex.Unlock()
   310  	return f.writer
   311  }
   312  
   313  func (f *fakeConnectionOpener) newWriter(info *api.Info) (psworker.MessageWriter, error) {
   314  	f.mutex.Lock()
   315  	defer f.mutex.Unlock()
   316  	if f.callback != nil {
   317  		f.callback(info)
   318  	}
   319  	if f.err != nil {
   320  		return nil, f.err
   321  	}
   322  	f.writer = &messageWriter{err: f.forwardErr}
   323  	return f.writer, nil
   324  }
   325  
   326  type messageWriter struct {
   327  	messages []*params.PubSubMessage
   328  	mutex    sync.Mutex
   329  	err      error
   330  }
   331  
   332  func (m *messageWriter) count() int {
   333  	m.mutex.Lock()
   334  	defer m.mutex.Unlock()
   335  	return len(m.messages)
   336  }
   337  
   338  func (m *messageWriter) ForwardMessage(message *params.PubSubMessage) error {
   339  	m.mutex.Lock()
   340  	defer m.mutex.Unlock()
   341  	if m.err != nil {
   342  		return m.err
   343  	}
   344  	m.messages = append(m.messages, message)
   345  	return nil
   346  }
   347  
   348  func (*messageWriter) Close() {}