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