github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/metrics/collect/manifold_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package collect_test
     5  
     6  import (
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils"
    14  	gc "gopkg.in/check.v1"
    15  	corecharm "gopkg.in/juju/charm.v6"
    16  	"gopkg.in/juju/names.v2"
    17  	"gopkg.in/juju/worker.v1/dependency"
    18  	dt "gopkg.in/juju/worker.v1/dependency/testing"
    19  
    20  	"github.com/juju/juju/agent"
    21  	coretesting "github.com/juju/juju/testing"
    22  	"github.com/juju/juju/worker/fortress"
    23  	"github.com/juju/juju/worker/metrics/collect"
    24  	"github.com/juju/juju/worker/metrics/spool"
    25  	"github.com/juju/juju/worker/uniter/runner/context"
    26  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    27  )
    28  
    29  type ManifoldSuite struct {
    30  	coretesting.BaseSuite
    31  
    32  	dataDir  string
    33  	oldLcAll string
    34  
    35  	manifoldConfig collect.ManifoldConfig
    36  	manifold       dependency.Manifold
    37  	resources      dt.StubResources
    38  }
    39  
    40  var _ = gc.Suite(&ManifoldSuite{})
    41  
    42  func (s *ManifoldSuite) SetUpTest(c *gc.C) {
    43  	s.BaseSuite.SetUpTest(c)
    44  	s.manifoldConfig = collect.ManifoldConfig{
    45  		AgentName:       "agent-name",
    46  		MetricSpoolName: "metric-spool-name",
    47  		CharmDirName:    "charmdir-name",
    48  	}
    49  	s.manifold = collect.Manifold(s.manifoldConfig)
    50  	s.dataDir = c.MkDir()
    51  
    52  	// create unit agent base dir so that hooks can run.
    53  	err := os.MkdirAll(filepath.Join(s.dataDir, "agents", "unit-u-0"), 0777)
    54  	c.Assert(err, jc.ErrorIsNil)
    55  
    56  	s.resources = dt.StubResources{
    57  		"agent-name":        dt.NewStubResource(&dummyAgent{dataDir: s.dataDir}),
    58  		"metric-spool-name": dt.NewStubResource(&dummyMetricFactory{}),
    59  		"charmdir-name":     dt.NewStubResource(&dummyCharmdir{aborted: false}),
    60  	}
    61  }
    62  
    63  // TestInputs ensures the collect manifold has the expected defined inputs.
    64  func (s *ManifoldSuite) TestInputs(c *gc.C) {
    65  	c.Check(s.manifold.Inputs, jc.DeepEquals, []string{
    66  		"agent-name", "metric-spool-name", "charmdir-name",
    67  	})
    68  }
    69  
    70  // TestStartMissingDeps ensures that the manifold correctly handles a missing
    71  // resource dependency.
    72  func (s *ManifoldSuite) TestStartMissingDeps(c *gc.C) {
    73  	for _, missingDep := range []string{
    74  		"agent-name", "metric-spool-name", "charmdir-name",
    75  	} {
    76  		testResources := dt.StubResources{}
    77  		for k, v := range s.resources {
    78  			if k == missingDep {
    79  				testResources[k] = dt.StubResource{Error: dependency.ErrMissing}
    80  			} else {
    81  				testResources[k] = v
    82  			}
    83  		}
    84  		worker, err := s.manifold.Start(testResources.Context())
    85  		c.Check(worker, gc.IsNil)
    86  		c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing)
    87  	}
    88  }
    89  
    90  // TestCollectWorkerStarts ensures that the manifold correctly sets up the worker.
    91  func (s *ManifoldSuite) TestCollectWorkerStarts(c *gc.C) {
    92  	s.PatchValue(collect.NewRecorder,
    93  		func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) {
    94  			// Return a dummyRecorder here, because otherwise a real one
    95  			// *might* get instantiated and error out, if the periodic worker
    96  			// happens to fire before the worker shuts down (as seen in
    97  			// LP:#1497355).
    98  			return &dummyRecorder{
    99  				charmURL: "cs:ubuntu-1",
   100  				unitTag:  "ubuntu/0",
   101  			}, nil
   102  		})
   103  	s.PatchValue(collect.ReadCharm,
   104  		func(_ names.UnitTag, _ context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) {
   105  			return corecharm.MustParseURL("cs:ubuntu-1"), map[string]corecharm.Metric{"pings": {Description: "test metric", Type: corecharm.MetricTypeAbsolute}}, nil
   106  		})
   107  	worker, err := s.manifold.Start(s.resources.Context())
   108  	c.Assert(err, jc.ErrorIsNil)
   109  	c.Assert(worker, gc.NotNil)
   110  	worker.Kill()
   111  	err = worker.Wait()
   112  	c.Assert(err, jc.ErrorIsNil)
   113  }
   114  
   115  func (s *ManifoldSuite) TestCollectWorkerErrorStopsListener(c *gc.C) {
   116  	s.PatchValue(collect.NewRecorder,
   117  		func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) {
   118  			return nil, errors.New("blah")
   119  		})
   120  	listener := &mockListener{}
   121  	s.PatchValue(collect.NewSocketListener, collect.NewSocketListenerFnc(listener))
   122  	s.PatchValue(collect.ReadCharm,
   123  		func(_ names.UnitTag, _ context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) {
   124  			return corecharm.MustParseURL("local:ubuntu-1"), map[string]corecharm.Metric{"pings": {Description: "test metric", Type: corecharm.MetricTypeAbsolute}}, nil
   125  		})
   126  	worker, err := s.manifold.Start(s.resources.Context())
   127  	c.Assert(err, jc.ErrorIsNil)
   128  	c.Assert(worker, gc.NotNil)
   129  	err = worker.Wait()
   130  	c.Assert(err, gc.ErrorMatches, ".*blah")
   131  	listener.CheckCallNames(c, "Stop")
   132  }
   133  
   134  type errorRecorder struct {
   135  	*spool.JSONMetricRecorder
   136  }
   137  
   138  func (e *errorRecorder) AddMetric(
   139  	key, value string, created time.Time, labels map[string]string) (err error) {
   140  	return e.JSONMetricRecorder.AddMetric(key, "bad", created, labels)
   141  }
   142  
   143  func (s *ManifoldSuite) TestRecordMetricsError(c *gc.C) {
   144  	// An error recording a metric does not propagate the error
   145  	// to the worker which could cause a bounce.
   146  	recorder, err := spool.NewJSONMetricRecorder(
   147  		spool.MetricRecorderConfig{
   148  			SpoolDir: c.MkDir(),
   149  			Metrics: map[string]corecharm.Metric{
   150  				"juju-units": {},
   151  			},
   152  			CharmURL: "local:precise/wordpress",
   153  			UnitTag:  "unit-wordpress-0",
   154  		})
   155  	c.Assert(err, jc.ErrorIsNil)
   156  	s.PatchValue(collect.NewRecorder,
   157  		func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) {
   158  			return &errorRecorder{recorder}, nil
   159  		})
   160  	s.PatchValue(collect.ReadCharm,
   161  		func(_ names.UnitTag, _ context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) {
   162  			return corecharm.MustParseURL("cs:wordpress-37"), nil, nil
   163  		})
   164  	collectEntity, err := collect.NewCollect(s.manifoldConfig, s.resources.Context())
   165  	c.Assert(err, jc.ErrorIsNil)
   166  	err = collectEntity.Do(nil)
   167  	c.Assert(err, jc.ErrorIsNil)
   168  }
   169  
   170  // TestJujuUnitsBuiltinMetric tests that the juju-units built-in metric is collected
   171  // with a mock implementation of newRecorder.
   172  func (s *ManifoldSuite) TestJujuUnitsBuiltinMetric(c *gc.C) {
   173  	recorder := &dummyRecorder{
   174  		charmURL:         "cs:wordpress-37",
   175  		unitTag:          "wp/0",
   176  		isDeclaredMetric: true,
   177  	}
   178  	s.PatchValue(collect.NewRecorder,
   179  		func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) {
   180  			return recorder, nil
   181  		})
   182  	s.PatchValue(collect.ReadCharm,
   183  		func(_ names.UnitTag, _ context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) {
   184  			return corecharm.MustParseURL("cs:wordpress-37"), map[string]corecharm.Metric{"pings": {Description: "test metric", Type: corecharm.MetricTypeAbsolute}}, nil
   185  		})
   186  	collectEntity, err := collect.NewCollect(s.manifoldConfig, s.resources.Context())
   187  	c.Assert(err, jc.ErrorIsNil)
   188  	err = collectEntity.Do(nil)
   189  	c.Assert(err, jc.ErrorIsNil)
   190  	c.Assert(recorder.closed, jc.IsTrue)
   191  	c.Assert(recorder.batches, gc.HasLen, 1)
   192  	c.Assert(recorder.batches[0].CharmURL, gc.Equals, "cs:wordpress-37")
   193  	c.Assert(recorder.batches[0].UnitTag, gc.Equals, "wp/0")
   194  	c.Assert(recorder.batches[0].Metrics, gc.HasLen, 1)
   195  	c.Assert(recorder.batches[0].Metrics[0].Key, gc.Equals, "juju-units")
   196  	c.Assert(recorder.batches[0].Metrics[0].Value, gc.Equals, "1")
   197  }
   198  
   199  // TestAvailability tests that the charmdir resource is properly checked.
   200  func (s *ManifoldSuite) TestAvailability(c *gc.C) {
   201  	recorder := &dummyRecorder{
   202  		charmURL:         "cs:wordpress-37",
   203  		unitTag:          "wp/0",
   204  		isDeclaredMetric: true,
   205  	}
   206  	s.PatchValue(collect.NewRecorder,
   207  		func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) {
   208  			return recorder, nil
   209  		})
   210  	s.PatchValue(collect.ReadCharm,
   211  		func(_ names.UnitTag, _ context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) {
   212  			return corecharm.MustParseURL("cs:wordpress-37"), map[string]corecharm.Metric{"pings": {Description: "test metric", Type: corecharm.MetricTypeAbsolute}}, nil
   213  		})
   214  	charmdir := &dummyCharmdir{}
   215  	s.resources["charmdir-name"] = dt.NewStubResource(charmdir)
   216  	collectEntity, err := collect.NewCollect(s.manifoldConfig, s.resources.Context())
   217  	c.Assert(err, jc.ErrorIsNil)
   218  	charmdir.aborted = true
   219  	err = collectEntity.Do(nil)
   220  	c.Assert(err, jc.ErrorIsNil)
   221  	c.Assert(recorder.batches, gc.HasLen, 0)
   222  
   223  	charmdir = &dummyCharmdir{aborted: false}
   224  	s.resources["charmdir-name"] = dt.NewStubResource(charmdir)
   225  	collectEntity, err = collect.NewCollect(s.manifoldConfig, s.resources.Context())
   226  	c.Assert(err, jc.ErrorIsNil)
   227  	err = collectEntity.Do(nil)
   228  	c.Assert(err, jc.ErrorIsNil)
   229  	c.Assert(recorder.closed, jc.IsTrue)
   230  	c.Assert(recorder.batches, gc.HasLen, 1)
   231  }
   232  
   233  // TestNoMetricsDeclared tests that if metrics are not declared, none are
   234  // collected, not even builtin.
   235  func (s *ManifoldSuite) TestNoMetricsDeclared(c *gc.C) {
   236  	recorder := &dummyRecorder{
   237  		charmURL:         "cs:wordpress-37",
   238  		unitTag:          "wp/0",
   239  		isDeclaredMetric: false,
   240  	}
   241  	s.PatchValue(collect.NewRecorder,
   242  		func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) {
   243  			return recorder, nil
   244  		})
   245  	s.PatchValue(collect.ReadCharm,
   246  		func(_ names.UnitTag, _ context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) {
   247  			return corecharm.MustParseURL("cs:wordpress-37"), map[string]corecharm.Metric{}, nil
   248  		})
   249  	collectEntity, err := collect.NewCollect(s.manifoldConfig, s.resources.Context())
   250  	c.Assert(err, jc.ErrorIsNil)
   251  	err = collectEntity.Do(nil)
   252  	c.Assert(err, jc.ErrorIsNil)
   253  	c.Assert(recorder.closed, jc.IsTrue)
   254  	c.Assert(recorder.batches, gc.HasLen, 0)
   255  }
   256  
   257  // TestCharmDirAborted tests when the fortress gating the charm directory
   258  // aborts the collector.
   259  func (s *ManifoldSuite) TestCharmDirAborted(c *gc.C) {
   260  	charmdir := &dummyCharmdir{aborted: true}
   261  	s.resources["charmdir-name"] = dt.NewStubResource(charmdir)
   262  	_, err := collect.NewCollect(s.manifoldConfig, s.resources.Context())
   263  	c.Assert(errors.Cause(err), gc.Equals, fortress.ErrAborted)
   264  }
   265  
   266  type dummyAgent struct {
   267  	agent.Agent
   268  	dataDir string
   269  }
   270  
   271  func (a dummyAgent) CurrentConfig() agent.Config {
   272  	return &dummyAgentConfig{dataDir: a.dataDir}
   273  }
   274  
   275  type dummyAgentConfig struct {
   276  	agent.Config
   277  	dataDir string
   278  }
   279  
   280  // Tag implements agent.AgentConfig.
   281  func (ac dummyAgentConfig) Tag() names.Tag {
   282  	return names.NewUnitTag("u/0")
   283  }
   284  
   285  // DataDir implements agent.AgentConfig.
   286  func (ac dummyAgentConfig) DataDir() string {
   287  	return ac.dataDir
   288  }
   289  
   290  type dummyCharmdir struct {
   291  	fortress.Guest
   292  
   293  	aborted bool
   294  }
   295  
   296  func (a *dummyCharmdir) Visit(visit fortress.Visit, _ fortress.Abort) error {
   297  	if a.aborted {
   298  		return fortress.ErrAborted
   299  	}
   300  	return visit()
   301  }
   302  
   303  type dummyMetricFactory struct {
   304  	spool.MetricFactory
   305  }
   306  
   307  type dummyRecorder struct {
   308  	spool.MetricRecorder
   309  
   310  	// inputs
   311  	charmURL, unitTag string
   312  	metrics           map[string]corecharm.Metric
   313  	isDeclaredMetric  bool
   314  	err               string
   315  
   316  	// outputs
   317  	closed  bool
   318  	batches []spool.MetricBatch
   319  }
   320  
   321  func (r *dummyRecorder) AddMetric(
   322  	key, value string, created time.Time, labels map[string]string) error {
   323  	if r.err != "" {
   324  		return errors.New(r.err)
   325  	}
   326  	then := time.Date(2015, 8, 20, 15, 48, 0, 0, time.UTC)
   327  	r.batches = append(r.batches, spool.MetricBatch{
   328  		CharmURL: r.charmURL,
   329  		UUID:     utils.MustNewUUID().String(),
   330  		Created:  then,
   331  		Metrics: []jujuc.Metric{{
   332  			Key:    key,
   333  			Value:  value,
   334  			Time:   then,
   335  			Labels: labels,
   336  		}},
   337  		UnitTag: r.unitTag,
   338  	})
   339  	return nil
   340  }
   341  
   342  func (r *dummyRecorder) IsDeclaredMetric(key string) bool {
   343  	if r.isDeclaredMetric {
   344  		return true
   345  	}
   346  	_, ok := r.metrics[key]
   347  	return ok
   348  }
   349  
   350  func (r *dummyRecorder) Close() error {
   351  	r.closed = true
   352  	return nil
   353  }