github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/worker/metrics/sender/sender_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package sender_test
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"net"
    11  	"path"
    12  	"runtime"
    13  	"time"
    14  
    15  	"github.com/juju/testing"
    16  	jc "github.com/juju/testing/checkers"
    17  	gc "gopkg.in/check.v1"
    18  	corecharm "gopkg.in/juju/charm.v6-unstable"
    19  
    20  	"github.com/juju/juju/apiserver/params"
    21  	"github.com/juju/juju/worker/metrics/sender"
    22  	"github.com/juju/juju/worker/metrics/spool"
    23  )
    24  
    25  var _ = gc.Suite(&senderSuite{})
    26  
    27  type senderSuite struct {
    28  	spoolDir      string
    29  	socketDir     string
    30  	metricfactory spool.MetricFactory
    31  }
    32  
    33  func (s *senderSuite) SetUpTest(c *gc.C) {
    34  	s.spoolDir = c.MkDir()
    35  	s.socketDir = c.MkDir()
    36  
    37  	s.metricfactory = &stubMetricFactory{
    38  		&testing.Stub{},
    39  		s.spoolDir,
    40  	}
    41  
    42  	declaredMetrics := map[string]corecharm.Metric{
    43  		"pings": corecharm.Metric{Description: "test pings", Type: corecharm.MetricTypeAbsolute},
    44  	}
    45  	recorder, err := s.metricfactory.Recorder(declaredMetrics, "local:trusty/testcharm", "testcharm/0")
    46  	c.Assert(err, jc.ErrorIsNil)
    47  
    48  	err = recorder.AddMetric("pings", "50", time.Now())
    49  	c.Assert(err, jc.ErrorIsNil)
    50  
    51  	err = recorder.Close()
    52  	c.Assert(err, jc.ErrorIsNil)
    53  
    54  	reader, err := s.metricfactory.Reader()
    55  	c.Assert(err, jc.ErrorIsNil)
    56  	batches, err := reader.Read()
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	c.Assert(batches, gc.HasLen, 1)
    59  
    60  	testing.PatchValue(sender.SocketName, func(_, _ string) string {
    61  		return sockPath(c)
    62  	})
    63  }
    64  
    65  func (s *senderSuite) TestHandler(c *gc.C) {
    66  	apiSender := newTestAPIMetricSender()
    67  	tmpDir := c.MkDir()
    68  	metricFactory := &stubMetricFactory{
    69  		&testing.Stub{},
    70  		tmpDir,
    71  	}
    72  
    73  	declaredMetrics := map[string]corecharm.Metric{
    74  		"pings": corecharm.Metric{Description: "test pings", Type: corecharm.MetricTypeAbsolute},
    75  	}
    76  	recorder, err := metricFactory.Recorder(declaredMetrics, "local:trusty/testcharm", "testcharm/0")
    77  	c.Assert(err, jc.ErrorIsNil)
    78  
    79  	err = recorder.AddMetric("pings", "50", time.Now())
    80  	c.Assert(err, jc.ErrorIsNil)
    81  
    82  	err = recorder.Close()
    83  	c.Assert(err, jc.ErrorIsNil)
    84  
    85  	metricSender, err := sender.NewSender(apiSender, s.metricfactory, s.socketDir, "")
    86  	c.Assert(err, jc.ErrorIsNil)
    87  
    88  	conn := &mockConnection{data: []byte(fmt.Sprintf("%v\n", tmpDir))}
    89  	ch := make(chan struct{})
    90  	err = metricSender.Handle(conn, ch)
    91  	c.Assert(err, jc.ErrorIsNil)
    92  
    93  	c.Assert(apiSender.batches, gc.HasLen, 1)
    94  	c.Assert(apiSender.batches[0].Tag, gc.Equals, "testcharm/0")
    95  	c.Assert(apiSender.batches[0].Batch.CharmURL, gc.Equals, "local:trusty/testcharm")
    96  	c.Assert(apiSender.batches[0].Batch.Metrics, gc.HasLen, 1)
    97  	c.Assert(apiSender.batches[0].Batch.Metrics[0].Key, gc.Equals, "pings")
    98  	c.Assert(apiSender.batches[0].Batch.Metrics[0].Value, gc.Equals, "50")
    99  }
   100  
   101  func (s *senderSuite) TestMetricSendingSuccess(c *gc.C) {
   102  	apiSender := newTestAPIMetricSender()
   103  
   104  	metricSender, err := sender.NewSender(apiSender, s.metricfactory, s.socketDir, "test-unit-0")
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	stopCh := make(chan struct{})
   107  	err = metricSender.Do(stopCh)
   108  	c.Assert(err, jc.ErrorIsNil)
   109  
   110  	c.Assert(apiSender.batches, gc.HasLen, 1)
   111  
   112  	reader, err := spool.NewJSONMetricReader(s.spoolDir)
   113  	c.Assert(err, jc.ErrorIsNil)
   114  	batches, err := reader.Read()
   115  	c.Assert(err, jc.ErrorIsNil)
   116  	c.Assert(batches, gc.HasLen, 0)
   117  }
   118  
   119  func (s *senderSuite) TestSendingGetDuplicate(c *gc.C) {
   120  	apiSender := newTestAPIMetricSender()
   121  
   122  	apiErr := &params.Error{Message: "already exists", Code: params.CodeAlreadyExists}
   123  	select {
   124  	case apiSender.errors <- apiErr:
   125  	default:
   126  		c.Fatalf("blocked error channel")
   127  	}
   128  
   129  	metricSender, err := sender.NewSender(apiSender, s.metricfactory, s.socketDir, "test-unit-0")
   130  	c.Assert(err, jc.ErrorIsNil)
   131  	stopCh := make(chan struct{})
   132  	err = metricSender.Do(stopCh)
   133  	c.Assert(err, jc.ErrorIsNil)
   134  
   135  	c.Assert(apiSender.batches, gc.HasLen, 1)
   136  
   137  	reader, err := spool.NewJSONMetricReader(s.spoolDir)
   138  	c.Assert(err, jc.ErrorIsNil)
   139  	batches, err := reader.Read()
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	c.Assert(batches, gc.HasLen, 0)
   142  }
   143  
   144  func (s *senderSuite) TestSendingFails(c *gc.C) {
   145  	apiSender := newTestAPIMetricSender()
   146  
   147  	select {
   148  	case apiSender.sendError <- errors.New("something went wrong"):
   149  	default:
   150  		c.Fatalf("blocked error channel")
   151  	}
   152  
   153  	metricSender, err := sender.NewSender(apiSender, s.metricfactory, s.socketDir, "test-unit-0")
   154  	c.Assert(err, jc.ErrorIsNil)
   155  	stopCh := make(chan struct{})
   156  	err = metricSender.Do(stopCh)
   157  	c.Assert(err, gc.ErrorMatches, "could not send metrics: something went wrong")
   158  
   159  	c.Assert(apiSender.batches, gc.HasLen, 1)
   160  
   161  	reader, err := spool.NewJSONMetricReader(s.spoolDir)
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	batches, err := reader.Read()
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	c.Assert(batches, gc.HasLen, 1)
   166  }
   167  
   168  func (s *senderSuite) TestNoSpoolDirectory(c *gc.C) {
   169  	apiSender := newTestAPIMetricSender()
   170  
   171  	metricfactory := &stubMetricFactory{
   172  		&testing.Stub{},
   173  		"/some/random/spool/dir",
   174  	}
   175  
   176  	metricSender, err := sender.NewSender(apiSender, metricfactory, s.socketDir, "")
   177  	c.Assert(err, jc.ErrorIsNil)
   178  	stopCh := make(chan struct{})
   179  	err = metricSender.Do(stopCh)
   180  	c.Assert(err, gc.ErrorMatches, `failed to open spool directory "/some/random/spool/dir": .*`)
   181  
   182  	c.Assert(apiSender.batches, gc.HasLen, 0)
   183  }
   184  
   185  func (s *senderSuite) TestNoMetricsToSend(c *gc.C) {
   186  	apiSender := newTestAPIMetricSender()
   187  
   188  	newTmpSpoolDir := c.MkDir()
   189  	metricfactory := &stubMetricFactory{
   190  		&testing.Stub{},
   191  		newTmpSpoolDir,
   192  	}
   193  
   194  	metricSender, err := sender.NewSender(apiSender, metricfactory, s.socketDir, "test-unit-0")
   195  	c.Assert(err, jc.ErrorIsNil)
   196  	stopCh := make(chan struct{})
   197  	err = metricSender.Do(stopCh)
   198  	c.Assert(err, jc.ErrorIsNil)
   199  
   200  	c.Assert(apiSender.batches, gc.HasLen, 0)
   201  }
   202  
   203  func newTestAPIMetricSender() *testAPIMetricSender {
   204  	return &testAPIMetricSender{errors: make(chan error, 1), sendError: make(chan error, 1)}
   205  }
   206  
   207  type testAPIMetricSender struct {
   208  	batches   []params.MetricBatchParam
   209  	errors    chan error
   210  	sendError chan error
   211  }
   212  
   213  func (t *testAPIMetricSender) AddMetricBatches(batches []params.MetricBatchParam) (map[string]error, error) {
   214  	t.batches = batches
   215  
   216  	var err error
   217  	select {
   218  	case e := <-t.errors:
   219  		err = e
   220  	default:
   221  		err = (*params.Error)(nil)
   222  	}
   223  
   224  	var sendErr error
   225  	select {
   226  	case e := <-t.sendError:
   227  		sendErr = e
   228  	default:
   229  		sendErr = nil
   230  	}
   231  
   232  	errors := make(map[string]error)
   233  	for _, b := range batches {
   234  		errors[b.Batch.UUID] = err
   235  	}
   236  	return errors, sendErr
   237  }
   238  
   239  type stubMetricFactory struct {
   240  	*testing.Stub
   241  	spoolDir string
   242  }
   243  
   244  func (s *stubMetricFactory) Recorder(declaredMetrics map[string]corecharm.Metric, charmURL, unitTag string) (spool.MetricRecorder, error) {
   245  	s.MethodCall(s, "Recorder", declaredMetrics, charmURL, unitTag)
   246  	config := spool.MetricRecorderConfig{
   247  		SpoolDir: s.spoolDir,
   248  		Metrics:  declaredMetrics,
   249  		CharmURL: charmURL,
   250  		UnitTag:  unitTag,
   251  	}
   252  
   253  	return spool.NewJSONMetricRecorder(config)
   254  }
   255  
   256  func (s *stubMetricFactory) Reader() (spool.MetricReader, error) {
   257  	s.MethodCall(s, "Reader")
   258  	return spool.NewJSONMetricReader(s.spoolDir)
   259  
   260  }
   261  
   262  type mockConnection struct {
   263  	net.Conn
   264  	testing.Stub
   265  	data []byte
   266  }
   267  
   268  // SetDeadline implements the net.Conn interface.
   269  func (c *mockConnection) SetDeadline(t time.Time) error {
   270  	c.AddCall("SetDeadline", t)
   271  	return nil
   272  }
   273  
   274  // Write implements the net.Conn interface.
   275  func (c *mockConnection) Write(data []byte) (int, error) {
   276  	c.AddCall("Write", data)
   277  	c.data = data
   278  	return len(data), nil
   279  }
   280  
   281  // Close implements the net.Conn interface.
   282  func (c *mockConnection) Close() error {
   283  	c.AddCall("Close")
   284  	return nil
   285  }
   286  
   287  func (c *mockConnection) eof() bool {
   288  	return len(c.data) == 0
   289  }
   290  
   291  func (c *mockConnection) readByte() byte {
   292  	b := c.data[0]
   293  	c.data = c.data[1:]
   294  	return b
   295  }
   296  
   297  func (c *mockConnection) Read(p []byte) (n int, err error) {
   298  	if c.eof() {
   299  		err = io.EOF
   300  		return
   301  	}
   302  	if cp := cap(p); cp > 0 {
   303  		for n < cp {
   304  			p[n] = c.readByte()
   305  			n++
   306  			if c.eof() {
   307  				break
   308  			}
   309  		}
   310  	}
   311  	return
   312  }
   313  
   314  func sockPath(c *gc.C) string {
   315  	sockPath := path.Join(c.MkDir(), "test.listener")
   316  	if runtime.GOOS == "windows" {
   317  		return `\\.\pipe` + sockPath[2:]
   318  	}
   319  	return sockPath
   320  }