github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/metrics/sender/sender_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package sender_test 5 6 import ( 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "net" 12 "path" 13 "path/filepath" 14 "runtime" 15 "time" 16 17 "github.com/juju/testing" 18 jc "github.com/juju/testing/checkers" 19 gc "gopkg.in/check.v1" 20 corecharm "gopkg.in/juju/charm.v6" 21 22 "github.com/juju/juju/apiserver/params" 23 jujutesting "github.com/juju/juju/testing" 24 "github.com/juju/juju/worker/metrics/sender" 25 "github.com/juju/juju/worker/metrics/spool" 26 ) 27 28 var _ = gc.Suite(&senderSuite{}) 29 30 type senderSuite struct { 31 jujutesting.BaseSuite 32 33 spoolDir string 34 socketDir string 35 metricfactory spool.MetricFactory 36 } 37 38 func (s *senderSuite) SetUpTest(c *gc.C) { 39 s.BaseSuite.SetUpTest(c) 40 s.spoolDir = c.MkDir() 41 s.socketDir = c.MkDir() 42 43 s.metricfactory = &stubMetricFactory{ 44 &testing.Stub{}, 45 s.spoolDir, 46 } 47 48 declaredMetrics := map[string]corecharm.Metric{ 49 "pings": {Description: "test pings", Type: corecharm.MetricTypeAbsolute}, 50 "pongs": {Description: "test pongs", Type: corecharm.MetricTypeGauge}, 51 } 52 recorder, err := s.metricfactory.Recorder(declaredMetrics, "local:trusty/testcharm", "testcharm/0") 53 c.Assert(err, jc.ErrorIsNil) 54 55 err = recorder.AddMetric("pings", "50", time.Now(), nil) 56 c.Assert(err, jc.ErrorIsNil) 57 58 err = recorder.AddMetric("pongs", "51", time.Now(), map[string]string{"foo": "bar"}) 59 c.Assert(err, jc.ErrorIsNil) 60 61 err = recorder.Close() 62 c.Assert(err, jc.ErrorIsNil) 63 64 reader, err := s.metricfactory.Reader() 65 c.Assert(err, jc.ErrorIsNil) 66 batches, err := reader.Read() 67 c.Assert(err, jc.ErrorIsNil) 68 c.Assert(batches, gc.HasLen, 1) 69 70 testing.PatchValue(sender.SocketName, func(_, _ string) string { 71 return sockPath(c) 72 }) 73 } 74 75 func (s *senderSuite) TestHandler(c *gc.C) { 76 apiSender := newTestAPIMetricSender() 77 tmpDir := c.MkDir() 78 metricFactory := &stubMetricFactory{ 79 &testing.Stub{}, 80 tmpDir, 81 } 82 83 declaredMetrics := map[string]corecharm.Metric{ 84 "pings": {Description: "test pings", Type: corecharm.MetricTypeAbsolute}, 85 "pongs": {Description: "test pongs", Type: corecharm.MetricTypeGauge}, 86 } 87 recorder, err := metricFactory.Recorder(declaredMetrics, "local:trusty/testcharm", "testcharm/0") 88 c.Assert(err, jc.ErrorIsNil) 89 90 err = recorder.AddMetric("pings", "50", time.Now(), nil) 91 c.Assert(err, jc.ErrorIsNil) 92 93 err = recorder.AddMetric("pongs", "51", time.Now(), map[string]string{"foo": "bar"}) 94 c.Assert(err, jc.ErrorIsNil) 95 96 err = recorder.Close() 97 c.Assert(err, jc.ErrorIsNil) 98 99 metricSender, err := sender.NewSender(apiSender, s.metricfactory, s.socketDir, "") 100 c.Assert(err, jc.ErrorIsNil) 101 102 conn := &mockConnection{data: []byte(fmt.Sprintf("%v\n", tmpDir))} 103 ch := make(chan struct{}) 104 err = metricSender.Handle(conn, ch) 105 c.Assert(err, jc.ErrorIsNil) 106 107 c.Assert(apiSender.batches, gc.HasLen, 1) 108 c.Assert(apiSender.batches[0].Tag, gc.Equals, "testcharm/0") 109 c.Assert(apiSender.batches[0].Batch.CharmURL, gc.Equals, "local:trusty/testcharm") 110 c.Assert(apiSender.batches[0].Batch.Metrics, gc.HasLen, 2) 111 c.Assert(apiSender.batches[0].Batch.Metrics[0].Key, gc.Equals, "pings") 112 c.Assert(apiSender.batches[0].Batch.Metrics[0].Value, gc.Equals, "50") 113 c.Assert(apiSender.batches[0].Batch.Metrics[0].Labels, gc.HasLen, 0) 114 c.Assert(apiSender.batches[0].Batch.Metrics[1].Key, gc.Equals, "pongs") 115 c.Assert(apiSender.batches[0].Batch.Metrics[1].Value, gc.Equals, "51") 116 c.Assert(apiSender.batches[0].Batch.Metrics[1].Labels, gc.DeepEquals, map[string]string{"foo": "bar"}) 117 } 118 119 func (s *senderSuite) TestMetricSendingSuccess(c *gc.C) { 120 apiSender := newTestAPIMetricSender() 121 122 metricSender, err := sender.NewSender(apiSender, s.metricfactory, s.socketDir, "test-unit-0") 123 c.Assert(err, jc.ErrorIsNil) 124 stopCh := make(chan struct{}) 125 err = metricSender.Do(stopCh) 126 c.Assert(err, jc.ErrorIsNil) 127 128 c.Assert(apiSender.batches, gc.HasLen, 1) 129 130 reader, err := spool.NewJSONMetricReader(s.spoolDir) 131 c.Assert(err, jc.ErrorIsNil) 132 batches, err := reader.Read() 133 c.Assert(err, jc.ErrorIsNil) 134 c.Assert(batches, gc.HasLen, 0) 135 } 136 137 func (s *senderSuite) TestSendingGetDuplicate(c *gc.C) { 138 apiSender := newTestAPIMetricSender() 139 140 apiErr := ¶ms.Error{Message: "already exists", Code: params.CodeAlreadyExists} 141 select { 142 case apiSender.errors <- apiErr: 143 default: 144 c.Fatalf("blocked error channel") 145 } 146 147 metricSender, err := sender.NewSender(apiSender, s.metricfactory, s.socketDir, "test-unit-0") 148 c.Assert(err, jc.ErrorIsNil) 149 stopCh := make(chan struct{}) 150 err = metricSender.Do(stopCh) 151 c.Assert(err, jc.ErrorIsNil) 152 153 c.Assert(apiSender.batches, gc.HasLen, 1) 154 155 reader, err := spool.NewJSONMetricReader(s.spoolDir) 156 c.Assert(err, jc.ErrorIsNil) 157 batches, err := reader.Read() 158 c.Assert(err, jc.ErrorIsNil) 159 c.Assert(batches, gc.HasLen, 0) 160 } 161 162 func (s *senderSuite) TestSendingFails(c *gc.C) { 163 apiSender := newTestAPIMetricSender() 164 165 select { 166 case apiSender.sendError <- errors.New("something went wrong"): 167 default: 168 c.Fatalf("blocked error channel") 169 } 170 171 metricSender, err := sender.NewSender(apiSender, s.metricfactory, s.socketDir, "test-unit-0") 172 c.Assert(err, jc.ErrorIsNil) 173 stopCh := make(chan struct{}) 174 err = metricSender.Do(stopCh) 175 c.Assert(err, gc.ErrorMatches, "could not send metrics: something went wrong") 176 177 c.Assert(apiSender.batches, gc.HasLen, 1) 178 179 reader, err := spool.NewJSONMetricReader(s.spoolDir) 180 c.Assert(err, jc.ErrorIsNil) 181 batches, err := reader.Read() 182 c.Assert(err, jc.ErrorIsNil) 183 c.Assert(batches, gc.HasLen, 1) 184 } 185 186 func (s *senderSuite) TestDataErrorIgnored(c *gc.C) { 187 err := ioutil.WriteFile(filepath.Join(s.spoolDir, "foo.meta"), []byte{}, 0644) 188 c.Assert(err, jc.ErrorIsNil) 189 apiSender := newTestAPIMetricSender() 190 191 metricSender, err := sender.NewSender(apiSender, s.metricfactory, s.socketDir, "test-unit-0") 192 c.Assert(err, jc.ErrorIsNil) 193 stopCh := make(chan struct{}) 194 err = metricSender.Do(stopCh) 195 c.Assert(err, jc.ErrorIsNil) 196 c.Assert(apiSender.batches, gc.HasLen, 0) 197 } 198 199 func (s *senderSuite) TestNoSpoolDirectory(c *gc.C) { 200 apiSender := newTestAPIMetricSender() 201 202 metricfactory := &stubMetricFactory{ 203 &testing.Stub{}, 204 "/some/random/spool/dir", 205 } 206 207 metricSender, err := sender.NewSender(apiSender, metricfactory, s.socketDir, "") 208 c.Assert(err, jc.ErrorIsNil) 209 stopCh := make(chan struct{}) 210 err = metricSender.Do(stopCh) 211 c.Assert(err, gc.ErrorMatches, `failed to open spool directory "/some/random/spool/dir": .*`) 212 213 c.Assert(apiSender.batches, gc.HasLen, 0) 214 } 215 216 func (s *senderSuite) TestNoMetricsToSend(c *gc.C) { 217 apiSender := newTestAPIMetricSender() 218 219 newTmpSpoolDir := c.MkDir() 220 metricfactory := &stubMetricFactory{ 221 &testing.Stub{}, 222 newTmpSpoolDir, 223 } 224 225 metricSender, err := sender.NewSender(apiSender, metricfactory, s.socketDir, "test-unit-0") 226 c.Assert(err, jc.ErrorIsNil) 227 stopCh := make(chan struct{}) 228 err = metricSender.Do(stopCh) 229 c.Assert(err, jc.ErrorIsNil) 230 231 c.Assert(apiSender.batches, gc.HasLen, 0) 232 } 233 234 func newTestAPIMetricSender() *testAPIMetricSender { 235 return &testAPIMetricSender{errors: make(chan error, 1), sendError: make(chan error, 1)} 236 } 237 238 type testAPIMetricSender struct { 239 batches []params.MetricBatchParam 240 errors chan error 241 sendError chan error 242 } 243 244 func (t *testAPIMetricSender) AddMetricBatches(batches []params.MetricBatchParam) (map[string]error, error) { 245 t.batches = batches 246 247 var err error 248 select { 249 case e := <-t.errors: 250 err = e 251 default: 252 err = (*params.Error)(nil) 253 } 254 255 var sendErr error 256 select { 257 case e := <-t.sendError: 258 sendErr = e 259 default: 260 sendErr = nil 261 } 262 263 errors := make(map[string]error) 264 for _, b := range batches { 265 errors[b.Batch.UUID] = err 266 } 267 return errors, sendErr 268 } 269 270 type stubMetricFactory struct { 271 *testing.Stub 272 spoolDir string 273 } 274 275 func (s *stubMetricFactory) Recorder(declaredMetrics map[string]corecharm.Metric, charmURL, unitTag string) (spool.MetricRecorder, error) { 276 s.MethodCall(s, "Recorder", declaredMetrics, charmURL, unitTag) 277 config := spool.MetricRecorderConfig{ 278 SpoolDir: s.spoolDir, 279 Metrics: declaredMetrics, 280 CharmURL: charmURL, 281 UnitTag: unitTag, 282 } 283 284 return spool.NewJSONMetricRecorder(config) 285 } 286 287 func (s *stubMetricFactory) Reader() (spool.MetricReader, error) { 288 s.MethodCall(s, "Reader") 289 return spool.NewJSONMetricReader(s.spoolDir) 290 291 } 292 293 type mockConnection struct { 294 net.Conn 295 testing.Stub 296 data []byte 297 } 298 299 // SetDeadline implements the net.Conn interface. 300 func (c *mockConnection) SetDeadline(t time.Time) error { 301 c.AddCall("SetDeadline", t) 302 return nil 303 } 304 305 // Write implements the net.Conn interface. 306 func (c *mockConnection) Write(data []byte) (int, error) { 307 c.AddCall("Write", data) 308 c.data = data 309 return len(data), nil 310 } 311 312 // Close implements the net.Conn interface. 313 func (c *mockConnection) Close() error { 314 c.AddCall("Close") 315 return nil 316 } 317 318 func (c *mockConnection) eof() bool { 319 return len(c.data) == 0 320 } 321 322 func (c *mockConnection) readByte() byte { 323 b := c.data[0] 324 c.data = c.data[1:] 325 return b 326 } 327 328 func (c *mockConnection) Read(p []byte) (n int, err error) { 329 if c.eof() { 330 err = io.EOF 331 return 332 } 333 if cp := cap(p); cp > 0 { 334 for n < cp { 335 p[n] = c.readByte() 336 n++ 337 if c.eof() { 338 break 339 } 340 } 341 } 342 return 343 } 344 345 func sockPath(c *gc.C) string { 346 sockPath := path.Join(c.MkDir(), "test.listener") 347 if runtime.GOOS == "windows" { 348 return `\\.\pipe` + sockPath[2:] 349 } 350 return sockPath 351 }