github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/metricsender/sender_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package metricsender_test
     5  
     6  import (
     7  	"encoding/json"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"time"
    11  
    12  	"github.com/juju/clock"
    13  	"github.com/juju/clock/testclock"
    14  	wireformat "github.com/juju/romulus/wireformat/metrics"
    15  	jc "github.com/juju/testing/checkers"
    16  	"github.com/juju/utils"
    17  	gc "gopkg.in/check.v1"
    18  
    19  	"github.com/juju/juju/apiserver/facades/agent/metricsender"
    20  	jujujutesting "github.com/juju/juju/juju/testing"
    21  	"github.com/juju/juju/state"
    22  	"github.com/juju/juju/testing/factory"
    23  )
    24  
    25  type SenderSuite struct {
    26  	jujujutesting.JujuConnSuite
    27  	unit           *state.Unit
    28  	meteredService *state.Application
    29  	clock          clock.Clock
    30  }
    31  
    32  var _ = gc.Suite(&SenderSuite{})
    33  
    34  func (s *SenderSuite) SetUpTest(c *gc.C) {
    35  	s.JujuConnSuite.SetUpTest(c)
    36  	meteredCharm := s.Factory.MakeCharm(c, &factory.CharmParams{Name: "metered", URL: "cs:quantal/metered"})
    37  	s.meteredService = s.Factory.MakeApplication(c, &factory.ApplicationParams{Charm: meteredCharm})
    38  	s.unit = s.Factory.MakeUnit(c, &factory.UnitParams{Application: s.meteredService, SetCharmURL: true})
    39  	s.clock = testclock.NewClock(time.Now())
    40  }
    41  
    42  // startServer starts a test HTTP server, returning a function that should be
    43  // run at the end of the test to clean up.
    44  func (s *SenderSuite) startServer(c *gc.C, handler http.Handler) func() {
    45  	ts := httptest.NewServer(handler)
    46  	cleanup := metricsender.PatchHost(ts.URL)
    47  	return func() {
    48  		ts.Close()
    49  		cleanup()
    50  	}
    51  }
    52  
    53  var _ metricsender.MetricSender = (*metricsender.HTTPSender)(nil)
    54  
    55  // TestHTTPSender checks that if the default sender
    56  // is in use metrics get sent
    57  func (s *SenderSuite) TestHTTPSender(c *gc.C) {
    58  	metricCount := 3
    59  	expectedCharmURL, _ := s.unit.CharmURL()
    60  
    61  	receiverChan := make(chan wireformat.MetricBatch, metricCount)
    62  	cleanup := s.startServer(c, testHandler(c, receiverChan, nil, 0))
    63  	defer cleanup()
    64  
    65  	now := time.Now()
    66  	metrics := make([]*state.MetricBatch, metricCount)
    67  	for i := range metrics {
    68  		metrics[i] = s.Factory.MakeMetric(c, &factory.MetricParams{Unit: s.unit, Sent: false, Time: &now})
    69  	}
    70  	sender := metricsender.DefaultSenderFactory()("http://example.com")
    71  	err := metricsender.SendMetrics(TestSenderBackend{s.State, s.Model}, sender, s.clock, 10, true)
    72  	c.Assert(err, jc.ErrorIsNil)
    73  
    74  	c.Assert(receiverChan, gc.HasLen, metricCount)
    75  	close(receiverChan)
    76  	for batch := range receiverChan {
    77  		c.Assert(batch.CharmUrl, gc.Equals, expectedCharmURL.String())
    78  	}
    79  
    80  	for _, metric := range metrics {
    81  		m, err := s.State.MetricBatch(metric.UUID())
    82  		c.Assert(err, jc.ErrorIsNil)
    83  		c.Assert(m.Sent(), jc.IsTrue)
    84  	}
    85  }
    86  
    87  // StatusMap defines a type for a function that returns the status and information for a specified unit.
    88  type StatusMap func(unitName string) (unit string, status string, info string)
    89  
    90  func errorHandler(c *gc.C, errorCode int) http.HandlerFunc {
    91  	return func(w http.ResponseWriter, r *http.Request) {
    92  		w.WriteHeader(errorCode)
    93  	}
    94  }
    95  
    96  func testHandler(c *gc.C, batches chan<- wireformat.MetricBatch, statusMap StatusMap, gracePeriod time.Duration) http.HandlerFunc {
    97  	return func(w http.ResponseWriter, r *http.Request) {
    98  		c.Assert(r.Method, gc.Equals, "POST")
    99  		dec := json.NewDecoder(r.Body)
   100  		enc := json.NewEncoder(w)
   101  		var incoming []wireformat.MetricBatch
   102  		err := dec.Decode(&incoming)
   103  		c.Assert(err, jc.ErrorIsNil)
   104  
   105  		var resp = make(wireformat.EnvironmentResponses)
   106  		for _, batch := range incoming {
   107  			c.Logf("received metrics batch: %+v", batch)
   108  
   109  			resp.Ack(batch.ModelUUID, batch.UUID)
   110  
   111  			if statusMap != nil {
   112  				unitName, status, info := statusMap(batch.UnitName)
   113  				resp.SetUnitStatus(batch.ModelUUID, unitName, status, info)
   114  			}
   115  
   116  			select {
   117  			case batches <- batch:
   118  			default:
   119  			}
   120  		}
   121  		uuid, err := utils.NewUUID()
   122  		c.Assert(err, jc.ErrorIsNil)
   123  		err = enc.Encode(wireformat.Response{
   124  			UUID:           uuid.String(),
   125  			EnvResponses:   resp,
   126  			NewGracePeriod: gracePeriod,
   127  		})
   128  		c.Assert(err, jc.ErrorIsNil)
   129  	}
   130  }
   131  
   132  // TestErrorCodes checks that for a set of error codes SendMetrics returns an
   133  // error and metrics are marked as not being sent
   134  func (s *SenderSuite) TestErrorCodes(c *gc.C) {
   135  	tests := []struct {
   136  		errorCode   int
   137  		expectedErr string
   138  	}{
   139  		{http.StatusBadRequest, "failed to send metrics http 400"},
   140  		{http.StatusServiceUnavailable, "failed to send metrics http 503"},
   141  	}
   142  
   143  	for _, test := range tests {
   144  		killServer := s.startServer(c, errorHandler(c, test.errorCode))
   145  
   146  		now := time.Now()
   147  		batches := make([]*state.MetricBatch, 3)
   148  		for i := range batches {
   149  			batches[i] = s.Factory.MakeMetric(c, &factory.MetricParams{Unit: s.unit, Sent: false, Time: &now})
   150  		}
   151  		sender := metricsender.DefaultSenderFactory()("http://example.com")
   152  		err := metricsender.SendMetrics(TestSenderBackend{s.State, s.Model}, sender, s.clock, 10, true)
   153  		c.Assert(err, gc.ErrorMatches, test.expectedErr)
   154  		for _, batch := range batches {
   155  			m, err := s.State.MetricBatch(batch.UUID())
   156  			c.Assert(err, jc.ErrorIsNil)
   157  			c.Assert(m.Sent(), jc.IsFalse)
   158  		}
   159  		killServer()
   160  	}
   161  }
   162  
   163  // TestMeterStatus checks that the meter status information returned
   164  // by the collector service is propagated to the unit.
   165  // is in use metrics get sent
   166  func (s *SenderSuite) TestMeterStatus(c *gc.C) {
   167  	statusFunc := func(unitName string) (string, string, string) {
   168  		return unitName, "GREEN", ""
   169  	}
   170  
   171  	cleanup := s.startServer(c, testHandler(c, nil, statusFunc, 0))
   172  	defer cleanup()
   173  
   174  	_ = s.Factory.MakeMetric(c, &factory.MetricParams{Unit: s.unit, Sent: false})
   175  
   176  	status, err := s.unit.GetMeterStatus()
   177  	c.Assert(err, jc.ErrorIsNil)
   178  	c.Assert(status.Code, gc.Equals, state.MeterNotSet)
   179  
   180  	sender := metricsender.DefaultSenderFactory()("http://example.com")
   181  	err = metricsender.SendMetrics(TestSenderBackend{s.State, s.Model}, sender, s.clock, 10, true)
   182  	c.Assert(err, jc.ErrorIsNil)
   183  
   184  	status, err = s.unit.GetMeterStatus()
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	c.Assert(status.Code, gc.Equals, state.MeterGreen)
   187  }
   188  
   189  // TestMeterStatusInvalid checks that the metric sender deals with invalid
   190  // meter status data properly.
   191  func (s *SenderSuite) TestMeterStatusInvalid(c *gc.C) {
   192  	unit1 := s.Factory.MakeUnit(c, &factory.UnitParams{Application: s.meteredService, SetCharmURL: true})
   193  	unit2 := s.Factory.MakeUnit(c, &factory.UnitParams{Application: s.meteredService, SetCharmURL: true})
   194  	unit3 := s.Factory.MakeUnit(c, &factory.UnitParams{Application: s.meteredService, SetCharmURL: true})
   195  
   196  	statusFunc := func(unitName string) (string, string, string) {
   197  		switch unitName {
   198  		case unit1.Name():
   199  			// valid meter status
   200  			return unitName, "GREEN", ""
   201  		case unit2.Name():
   202  			// invalid meter status
   203  			return unitName, "blah", ""
   204  		case unit3.Name():
   205  			// invalid unit name
   206  			return "no-such-unit", "GREEN", ""
   207  		default:
   208  			return unitName, "GREEN", ""
   209  		}
   210  	}
   211  
   212  	cleanup := s.startServer(c, testHandler(c, nil, statusFunc, 0))
   213  	defer cleanup()
   214  
   215  	_ = s.Factory.MakeMetric(c, &factory.MetricParams{Unit: unit1, Sent: false})
   216  	_ = s.Factory.MakeMetric(c, &factory.MetricParams{Unit: unit2, Sent: false})
   217  	_ = s.Factory.MakeMetric(c, &factory.MetricParams{Unit: unit3, Sent: false})
   218  
   219  	for _, unit := range []*state.Unit{unit1, unit2, unit3} {
   220  		status, err := unit.GetMeterStatus()
   221  		c.Assert(err, jc.ErrorIsNil)
   222  		c.Assert(status.Code, gc.Equals, state.MeterNotSet)
   223  	}
   224  
   225  	sender := metricsender.DefaultSenderFactory()("http://example.com")
   226  	err := metricsender.SendMetrics(TestSenderBackend{s.State, s.Model}, sender, s.clock, 10, true)
   227  	c.Assert(err, jc.ErrorIsNil)
   228  
   229  	status, err := unit1.GetMeterStatus()
   230  	c.Assert(err, jc.ErrorIsNil)
   231  	c.Assert(status.Code, gc.Equals, state.MeterGreen)
   232  
   233  	status, err = unit2.GetMeterStatus()
   234  	c.Assert(err, jc.ErrorIsNil)
   235  	c.Assert(status.Code, gc.Equals, state.MeterNotSet)
   236  
   237  	status, err = unit3.GetMeterStatus()
   238  	c.Assert(err, jc.ErrorIsNil)
   239  	c.Assert(status.Code, gc.Equals, state.MeterNotSet)
   240  
   241  }
   242  
   243  func (s *SenderSuite) TestGracePeriodResponse(c *gc.C) {
   244  	_ = s.Factory.MakeMetric(c, &factory.MetricParams{Unit: s.unit, Sent: false})
   245  	cleanup := s.startServer(c, testHandler(c, nil, nil, 47*time.Hour))
   246  	defer cleanup()
   247  	sender := metricsender.DefaultSenderFactory()("http://example.com")
   248  	err := metricsender.SendMetrics(TestSenderBackend{s.State, s.Model}, sender, s.clock, 10, true)
   249  	c.Assert(err, jc.ErrorIsNil)
   250  	mm, err := s.State.MetricsManager()
   251  	c.Assert(err, jc.ErrorIsNil)
   252  	c.Assert(mm.GracePeriod(), gc.Equals, 47*time.Hour)
   253  }
   254  
   255  func (s *SenderSuite) TestNegativeGracePeriodResponse(c *gc.C) {
   256  	_ = s.Factory.MakeMetric(c, &factory.MetricParams{Unit: s.unit, Sent: false})
   257  
   258  	cleanup := s.startServer(c, testHandler(c, nil, nil, -47*time.Hour))
   259  	defer cleanup()
   260  	sender := metricsender.DefaultSenderFactory()("http://example.com")
   261  	err := metricsender.SendMetrics(TestSenderBackend{s.State, s.Model}, sender, s.clock, 10, true)
   262  	c.Assert(err, jc.ErrorIsNil)
   263  	mm, err := s.State.MetricsManager()
   264  	c.Assert(err, jc.ErrorIsNil)
   265  	c.Assert(mm.GracePeriod(), gc.Equals, 24*time.Hour*7) //Default (unchanged)
   266  }
   267  
   268  func (s *SenderSuite) TestZeroGracePeriodResponse(c *gc.C) {
   269  	_ = s.Factory.MakeMetric(c, &factory.MetricParams{Unit: s.unit, Sent: false})
   270  
   271  	cleanup := s.startServer(c, testHandler(c, nil, nil, 0))
   272  	defer cleanup()
   273  	sender := metricsender.DefaultSenderFactory()("http://example.com")
   274  	err := metricsender.SendMetrics(TestSenderBackend{s.State, s.Model}, sender, s.clock, 10, true)
   275  	c.Assert(err, jc.ErrorIsNil)
   276  	mm, err := s.State.MetricsManager()
   277  	c.Assert(err, jc.ErrorIsNil)
   278  	c.Assert(mm.GracePeriod(), gc.Equals, 24*time.Hour*7) //Default (unchanged)
   279  }