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