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