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 }