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