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