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