github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/metrics/collect/handler_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package collect_test
     5  
     6  import (
     7  	"net"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"time"
    12  
    13  	corecharm "github.com/juju/charm/v12"
    14  	"github.com/juju/clock"
    15  	"github.com/juju/loggo"
    16  	"github.com/juju/names/v5"
    17  	"github.com/juju/testing"
    18  	jc "github.com/juju/testing/checkers"
    19  	"github.com/juju/worker/v3/dependency"
    20  	dt "github.com/juju/worker/v3/dependency/testing"
    21  	"github.com/juju/worker/v3/workertest"
    22  	gc "gopkg.in/check.v1"
    23  
    24  	coretesting "github.com/juju/juju/testing"
    25  	"github.com/juju/juju/worker/metrics/collect"
    26  	"github.com/juju/juju/worker/metrics/spool"
    27  	"github.com/juju/juju/worker/uniter/runner/context"
    28  )
    29  
    30  type handlerSuite struct {
    31  	coretesting.BaseSuite
    32  
    33  	manifoldConfig collect.ManifoldConfig
    34  	manifold       dependency.Manifold
    35  	dataDir        string
    36  	resources      dt.StubResources
    37  	recorder       *dummyRecorder
    38  	listener       *mockListener
    39  	mockReadCharm  *mockReadCharm
    40  }
    41  
    42  var _ = gc.Suite(&handlerSuite{})
    43  
    44  func (s *handlerSuite) SetUpTest(c *gc.C) {
    45  	s.BaseSuite.SetUpTest(c)
    46  	s.manifoldConfig = collect.ManifoldConfig{
    47  		AgentName:       "agent-name",
    48  		MetricSpoolName: "metric-spool-name",
    49  		CharmDirName:    "charmdir-name",
    50  		Clock:           clock.WallClock,
    51  		Logger:          loggo.GetLogger("test"),
    52  	}
    53  	s.manifold = collect.Manifold(s.manifoldConfig)
    54  	s.dataDir = c.MkDir()
    55  
    56  	// create unit agent base dir so that hooks can run.
    57  	err := os.MkdirAll(filepath.Join(s.dataDir, "agents", "unit-u-0"), 0777)
    58  	c.Assert(err, jc.ErrorIsNil)
    59  
    60  	s.recorder = &dummyRecorder{
    61  		charmURL: "local:trusty/metered-1",
    62  		unitTag:  "metered/0",
    63  		metrics: map[string]corecharm.Metric{
    64  			"pings": {
    65  				Description: "test metric",
    66  				Type:        corecharm.MetricTypeAbsolute,
    67  			},
    68  			"juju-units": {},
    69  		},
    70  	}
    71  
    72  	s.resources = dt.StubResources{
    73  		"agent-name":        dt.NewStubResource(&dummyAgent{dataDir: s.dataDir}),
    74  		"metric-spool-name": dt.NewStubResource(&mockMetricFactory{recorder: s.recorder}),
    75  		"charmdir-name":     dt.NewStubResource(&dummyCharmdir{aborted: false}),
    76  	}
    77  
    78  	s.PatchValue(collect.NewRecorder,
    79  		func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) {
    80  			// Return a dummyRecorder here, because otherwise a real one
    81  			// *might* get instantiated and error out, if the periodic worker
    82  			// happens to fire before the worker shuts down (as seen in
    83  			// LP:#1497355).
    84  			return &dummyRecorder{
    85  				charmURL: "local:trusty/metered-1",
    86  				unitTag:  "metered/0",
    87  				metrics: map[string]corecharm.Metric{
    88  					"pings": {
    89  						Description: "test metric",
    90  						Type:        corecharm.MetricTypeAbsolute,
    91  					},
    92  					"juju-units": {},
    93  				},
    94  			}, nil
    95  		},
    96  	)
    97  	s.mockReadCharm = &mockReadCharm{}
    98  	s.PatchValue(collect.ReadCharm, s.mockReadCharm.ReadCharm)
    99  	s.listener = &mockListener{}
   100  	s.PatchValue(collect.NewSocketListener, collect.NewSocketListenerFnc(s.listener))
   101  }
   102  
   103  func (s *handlerSuite) TestListenerStart(c *gc.C) {
   104  	worker, err := s.manifold.Start(s.resources.Context())
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	c.Assert(worker, gc.NotNil)
   107  	c.Assert(s.listener.Calls(), gc.HasLen, 0)
   108  	workertest.CleanKill(c, worker)
   109  	s.listener.CheckCall(c, 0, "Stop")
   110  }
   111  
   112  func (s *handlerSuite) TestJujuUnitsBuiltinMetric(c *gc.C) {
   113  	worker, err := s.manifold.Start(s.resources.Context())
   114  	c.Assert(err, jc.ErrorIsNil)
   115  	c.Assert(worker, gc.NotNil)
   116  	c.Assert(s.listener.Calls(), gc.HasLen, 0)
   117  
   118  	conn, err := s.listener.trigger()
   119  	c.Assert(err, jc.ErrorIsNil)
   120  	conn.CheckCallNames(c, "SetDeadline", "Write", "Close")
   121  
   122  	responseString := strings.Trim(string(conn.data), " \n\t")
   123  	c.Assert(responseString, gc.Equals, "ok")
   124  	c.Assert(s.recorder.batches, gc.HasLen, 1)
   125  
   126  	workertest.CleanKill(c, worker)
   127  	s.listener.CheckCall(c, 0, "Stop")
   128  }
   129  
   130  func (s *handlerSuite) TestReadCharmCalledOnEachTrigger(c *gc.C) {
   131  	worker, err := s.manifold.Start(s.resources.Context())
   132  	c.Assert(err, jc.ErrorIsNil)
   133  	c.Assert(worker, gc.NotNil)
   134  	c.Assert(s.listener.Calls(), gc.HasLen, 0)
   135  
   136  	_, err = s.listener.trigger()
   137  	c.Assert(err, jc.ErrorIsNil)
   138  	_, err = s.listener.trigger()
   139  	c.Assert(err, jc.ErrorIsNil)
   140  
   141  	s.PatchValue(collect.ReadCharm, s.mockReadCharm.ReadCharm)
   142  	workertest.CleanKill(c, worker)
   143  
   144  	// Expect 3 calls to ReadCharm, one on start and one per handler call
   145  	s.mockReadCharm.CheckCallNames(c, "ReadCharm", "ReadCharm", "ReadCharm")
   146  	s.listener.CheckCall(c, 0, "Stop")
   147  }
   148  
   149  func (s *handlerSuite) TestHandlerError(c *gc.C) {
   150  	worker, err := s.manifold.Start(s.resources.Context())
   151  	c.Assert(err, jc.ErrorIsNil)
   152  	c.Assert(worker, gc.NotNil)
   153  	c.Assert(s.listener.Calls(), gc.HasLen, 0)
   154  
   155  	s.recorder.err = "well, this is embarrassing"
   156  
   157  	conn, err := s.listener.trigger()
   158  	c.Assert(err, gc.ErrorMatches, "failed to collect metrics: error adding 'juju-units' metric: well, this is embarrassing")
   159  	conn.CheckCallNames(c, "SetDeadline", "Write", "Close")
   160  
   161  	responseString := strings.Trim(string(conn.data), " \n\t")
   162  	//c.Assert(responseString, gc.Matches, ".*well, this is embarrassing")
   163  	c.Assert(responseString, gc.Equals, `error: failed to collect metrics: error adding 'juju-units' metric: well, this is embarrassing`)
   164  	c.Assert(s.recorder.batches, gc.HasLen, 0)
   165  
   166  	workertest.CleanKill(c, worker)
   167  	s.listener.CheckCall(c, 0, "Stop")
   168  }
   169  
   170  type mockListener struct {
   171  	testing.Stub
   172  	handler spool.ConnectionHandler
   173  }
   174  
   175  func (l *mockListener) trigger() (*mockConnection, error) {
   176  	conn := &mockConnection{}
   177  	dying := make(chan struct{})
   178  	err := l.handler.Handle(conn, dying)
   179  	if err != nil {
   180  		return conn, err
   181  	}
   182  	return conn, nil
   183  }
   184  
   185  // Stop implements the stopper interface.
   186  func (l *mockListener) Stop() error {
   187  	l.AddCall("Stop")
   188  	return nil
   189  }
   190  
   191  func (l *mockListener) SetHandler(handler spool.ConnectionHandler) {
   192  	l.handler = handler
   193  }
   194  
   195  type mockConnection struct {
   196  	net.Conn
   197  	testing.Stub
   198  	data []byte
   199  }
   200  
   201  // SetDeadline implements the net.Conn interface.
   202  func (c *mockConnection) SetDeadline(t time.Time) error {
   203  	c.AddCall("SetDeadline", t)
   204  	return nil
   205  }
   206  
   207  // Write implements the net.Conn interface.
   208  func (c *mockConnection) Write(data []byte) (int, error) {
   209  	c.AddCall("Write", data)
   210  	c.data = make([]byte, len(data))
   211  	copy(c.data, data)
   212  	return len(data), nil
   213  }
   214  
   215  // Close implements the net.Conn interface.
   216  func (c *mockConnection) Close() error {
   217  	c.AddCall("Close")
   218  	return nil
   219  }
   220  
   221  type mockMetricFactory struct {
   222  	spool.MetricFactory
   223  	recorder *dummyRecorder
   224  }
   225  
   226  // Recorder implements the spool.MetricFactory interface.
   227  func (f *mockMetricFactory) Recorder(metrics map[string]corecharm.Metric, charmURL, unitTag string) (spool.MetricRecorder, error) {
   228  	return f.recorder, nil
   229  }
   230  
   231  type mockReadCharm struct {
   232  	testing.Stub
   233  }
   234  
   235  func (m *mockReadCharm) ReadCharm(unitTag names.UnitTag, paths context.Paths) (string, map[string]corecharm.Metric, error) {
   236  	m.MethodCall(m, "ReadCharm", unitTag, paths)
   237  	return "local:trusty/metered-1",
   238  		map[string]corecharm.Metric{
   239  			"pings":      {Description: "test metric", Type: corecharm.MetricTypeAbsolute},
   240  			"juju-units": {},
   241  		}, nil
   242  }