github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/metrics/collect/manifold_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package collect_test 5 6 import ( 7 "os" 8 "path/filepath" 9 "time" 10 11 "github.com/juju/errors" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/utils" 14 gc "gopkg.in/check.v1" 15 corecharm "gopkg.in/juju/charm.v6" 16 "gopkg.in/juju/names.v2" 17 "gopkg.in/juju/worker.v1/dependency" 18 dt "gopkg.in/juju/worker.v1/dependency/testing" 19 20 "github.com/juju/juju/agent" 21 coretesting "github.com/juju/juju/testing" 22 "github.com/juju/juju/worker/fortress" 23 "github.com/juju/juju/worker/metrics/collect" 24 "github.com/juju/juju/worker/metrics/spool" 25 "github.com/juju/juju/worker/uniter/runner/context" 26 "github.com/juju/juju/worker/uniter/runner/jujuc" 27 ) 28 29 type ManifoldSuite struct { 30 coretesting.BaseSuite 31 32 dataDir string 33 oldLcAll string 34 35 manifoldConfig collect.ManifoldConfig 36 manifold dependency.Manifold 37 resources dt.StubResources 38 } 39 40 var _ = gc.Suite(&ManifoldSuite{}) 41 42 func (s *ManifoldSuite) SetUpTest(c *gc.C) { 43 s.BaseSuite.SetUpTest(c) 44 s.manifoldConfig = collect.ManifoldConfig{ 45 AgentName: "agent-name", 46 MetricSpoolName: "metric-spool-name", 47 CharmDirName: "charmdir-name", 48 } 49 s.manifold = collect.Manifold(s.manifoldConfig) 50 s.dataDir = c.MkDir() 51 52 // create unit agent base dir so that hooks can run. 53 err := os.MkdirAll(filepath.Join(s.dataDir, "agents", "unit-u-0"), 0777) 54 c.Assert(err, jc.ErrorIsNil) 55 56 s.resources = dt.StubResources{ 57 "agent-name": dt.NewStubResource(&dummyAgent{dataDir: s.dataDir}), 58 "metric-spool-name": dt.NewStubResource(&dummyMetricFactory{}), 59 "charmdir-name": dt.NewStubResource(&dummyCharmdir{aborted: false}), 60 } 61 } 62 63 // TestInputs ensures the collect manifold has the expected defined inputs. 64 func (s *ManifoldSuite) TestInputs(c *gc.C) { 65 c.Check(s.manifold.Inputs, jc.DeepEquals, []string{ 66 "agent-name", "metric-spool-name", "charmdir-name", 67 }) 68 } 69 70 // TestStartMissingDeps ensures that the manifold correctly handles a missing 71 // resource dependency. 72 func (s *ManifoldSuite) TestStartMissingDeps(c *gc.C) { 73 for _, missingDep := range []string{ 74 "agent-name", "metric-spool-name", "charmdir-name", 75 } { 76 testResources := dt.StubResources{} 77 for k, v := range s.resources { 78 if k == missingDep { 79 testResources[k] = dt.StubResource{Error: dependency.ErrMissing} 80 } else { 81 testResources[k] = v 82 } 83 } 84 worker, err := s.manifold.Start(testResources.Context()) 85 c.Check(worker, gc.IsNil) 86 c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing) 87 } 88 } 89 90 // TestCollectWorkerStarts ensures that the manifold correctly sets up the worker. 91 func (s *ManifoldSuite) TestCollectWorkerStarts(c *gc.C) { 92 s.PatchValue(collect.NewRecorder, 93 func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) { 94 // Return a dummyRecorder here, because otherwise a real one 95 // *might* get instantiated and error out, if the periodic worker 96 // happens to fire before the worker shuts down (as seen in 97 // LP:#1497355). 98 return &dummyRecorder{ 99 charmURL: "cs:ubuntu-1", 100 unitTag: "ubuntu/0", 101 }, nil 102 }) 103 s.PatchValue(collect.ReadCharm, 104 func(_ names.UnitTag, _ context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) { 105 return corecharm.MustParseURL("cs:ubuntu-1"), map[string]corecharm.Metric{"pings": {Description: "test metric", Type: corecharm.MetricTypeAbsolute}}, nil 106 }) 107 worker, err := s.manifold.Start(s.resources.Context()) 108 c.Assert(err, jc.ErrorIsNil) 109 c.Assert(worker, gc.NotNil) 110 worker.Kill() 111 err = worker.Wait() 112 c.Assert(err, jc.ErrorIsNil) 113 } 114 115 func (s *ManifoldSuite) TestCollectWorkerErrorStopsListener(c *gc.C) { 116 s.PatchValue(collect.NewRecorder, 117 func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) { 118 return nil, errors.New("blah") 119 }) 120 listener := &mockListener{} 121 s.PatchValue(collect.NewSocketListener, collect.NewSocketListenerFnc(listener)) 122 s.PatchValue(collect.ReadCharm, 123 func(_ names.UnitTag, _ context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) { 124 return corecharm.MustParseURL("local:ubuntu-1"), map[string]corecharm.Metric{"pings": {Description: "test metric", Type: corecharm.MetricTypeAbsolute}}, nil 125 }) 126 worker, err := s.manifold.Start(s.resources.Context()) 127 c.Assert(err, jc.ErrorIsNil) 128 c.Assert(worker, gc.NotNil) 129 err = worker.Wait() 130 c.Assert(err, gc.ErrorMatches, ".*blah") 131 listener.CheckCallNames(c, "Stop") 132 } 133 134 type errorRecorder struct { 135 *spool.JSONMetricRecorder 136 } 137 138 func (e *errorRecorder) AddMetric( 139 key, value string, created time.Time, labels map[string]string) (err error) { 140 return e.JSONMetricRecorder.AddMetric(key, "bad", created, labels) 141 } 142 143 func (s *ManifoldSuite) TestRecordMetricsError(c *gc.C) { 144 // An error recording a metric does not propagate the error 145 // to the worker which could cause a bounce. 146 recorder, err := spool.NewJSONMetricRecorder( 147 spool.MetricRecorderConfig{ 148 SpoolDir: c.MkDir(), 149 Metrics: map[string]corecharm.Metric{ 150 "juju-units": {}, 151 }, 152 CharmURL: "local:precise/wordpress", 153 UnitTag: "unit-wordpress-0", 154 }) 155 c.Assert(err, jc.ErrorIsNil) 156 s.PatchValue(collect.NewRecorder, 157 func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) { 158 return &errorRecorder{recorder}, nil 159 }) 160 s.PatchValue(collect.ReadCharm, 161 func(_ names.UnitTag, _ context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) { 162 return corecharm.MustParseURL("cs:wordpress-37"), nil, nil 163 }) 164 collectEntity, err := collect.NewCollect(s.manifoldConfig, s.resources.Context()) 165 c.Assert(err, jc.ErrorIsNil) 166 err = collectEntity.Do(nil) 167 c.Assert(err, jc.ErrorIsNil) 168 } 169 170 // TestJujuUnitsBuiltinMetric tests that the juju-units built-in metric is collected 171 // with a mock implementation of newRecorder. 172 func (s *ManifoldSuite) TestJujuUnitsBuiltinMetric(c *gc.C) { 173 recorder := &dummyRecorder{ 174 charmURL: "cs:wordpress-37", 175 unitTag: "wp/0", 176 isDeclaredMetric: true, 177 } 178 s.PatchValue(collect.NewRecorder, 179 func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) { 180 return recorder, nil 181 }) 182 s.PatchValue(collect.ReadCharm, 183 func(_ names.UnitTag, _ context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) { 184 return corecharm.MustParseURL("cs:wordpress-37"), map[string]corecharm.Metric{"pings": {Description: "test metric", Type: corecharm.MetricTypeAbsolute}}, nil 185 }) 186 collectEntity, err := collect.NewCollect(s.manifoldConfig, s.resources.Context()) 187 c.Assert(err, jc.ErrorIsNil) 188 err = collectEntity.Do(nil) 189 c.Assert(err, jc.ErrorIsNil) 190 c.Assert(recorder.closed, jc.IsTrue) 191 c.Assert(recorder.batches, gc.HasLen, 1) 192 c.Assert(recorder.batches[0].CharmURL, gc.Equals, "cs:wordpress-37") 193 c.Assert(recorder.batches[0].UnitTag, gc.Equals, "wp/0") 194 c.Assert(recorder.batches[0].Metrics, gc.HasLen, 1) 195 c.Assert(recorder.batches[0].Metrics[0].Key, gc.Equals, "juju-units") 196 c.Assert(recorder.batches[0].Metrics[0].Value, gc.Equals, "1") 197 } 198 199 // TestAvailability tests that the charmdir resource is properly checked. 200 func (s *ManifoldSuite) TestAvailability(c *gc.C) { 201 recorder := &dummyRecorder{ 202 charmURL: "cs:wordpress-37", 203 unitTag: "wp/0", 204 isDeclaredMetric: true, 205 } 206 s.PatchValue(collect.NewRecorder, 207 func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) { 208 return recorder, nil 209 }) 210 s.PatchValue(collect.ReadCharm, 211 func(_ names.UnitTag, _ context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) { 212 return corecharm.MustParseURL("cs:wordpress-37"), map[string]corecharm.Metric{"pings": {Description: "test metric", Type: corecharm.MetricTypeAbsolute}}, nil 213 }) 214 charmdir := &dummyCharmdir{} 215 s.resources["charmdir-name"] = dt.NewStubResource(charmdir) 216 collectEntity, err := collect.NewCollect(s.manifoldConfig, s.resources.Context()) 217 c.Assert(err, jc.ErrorIsNil) 218 charmdir.aborted = true 219 err = collectEntity.Do(nil) 220 c.Assert(err, jc.ErrorIsNil) 221 c.Assert(recorder.batches, gc.HasLen, 0) 222 223 charmdir = &dummyCharmdir{aborted: false} 224 s.resources["charmdir-name"] = dt.NewStubResource(charmdir) 225 collectEntity, err = collect.NewCollect(s.manifoldConfig, s.resources.Context()) 226 c.Assert(err, jc.ErrorIsNil) 227 err = collectEntity.Do(nil) 228 c.Assert(err, jc.ErrorIsNil) 229 c.Assert(recorder.closed, jc.IsTrue) 230 c.Assert(recorder.batches, gc.HasLen, 1) 231 } 232 233 // TestNoMetricsDeclared tests that if metrics are not declared, none are 234 // collected, not even builtin. 235 func (s *ManifoldSuite) TestNoMetricsDeclared(c *gc.C) { 236 recorder := &dummyRecorder{ 237 charmURL: "cs:wordpress-37", 238 unitTag: "wp/0", 239 isDeclaredMetric: false, 240 } 241 s.PatchValue(collect.NewRecorder, 242 func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) { 243 return recorder, nil 244 }) 245 s.PatchValue(collect.ReadCharm, 246 func(_ names.UnitTag, _ context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) { 247 return corecharm.MustParseURL("cs:wordpress-37"), map[string]corecharm.Metric{}, nil 248 }) 249 collectEntity, err := collect.NewCollect(s.manifoldConfig, s.resources.Context()) 250 c.Assert(err, jc.ErrorIsNil) 251 err = collectEntity.Do(nil) 252 c.Assert(err, jc.ErrorIsNil) 253 c.Assert(recorder.closed, jc.IsTrue) 254 c.Assert(recorder.batches, gc.HasLen, 0) 255 } 256 257 // TestCharmDirAborted tests when the fortress gating the charm directory 258 // aborts the collector. 259 func (s *ManifoldSuite) TestCharmDirAborted(c *gc.C) { 260 charmdir := &dummyCharmdir{aborted: true} 261 s.resources["charmdir-name"] = dt.NewStubResource(charmdir) 262 _, err := collect.NewCollect(s.manifoldConfig, s.resources.Context()) 263 c.Assert(errors.Cause(err), gc.Equals, fortress.ErrAborted) 264 } 265 266 type dummyAgent struct { 267 agent.Agent 268 dataDir string 269 } 270 271 func (a dummyAgent) CurrentConfig() agent.Config { 272 return &dummyAgentConfig{dataDir: a.dataDir} 273 } 274 275 type dummyAgentConfig struct { 276 agent.Config 277 dataDir string 278 } 279 280 // Tag implements agent.AgentConfig. 281 func (ac dummyAgentConfig) Tag() names.Tag { 282 return names.NewUnitTag("u/0") 283 } 284 285 // DataDir implements agent.AgentConfig. 286 func (ac dummyAgentConfig) DataDir() string { 287 return ac.dataDir 288 } 289 290 type dummyCharmdir struct { 291 fortress.Guest 292 293 aborted bool 294 } 295 296 func (a *dummyCharmdir) Visit(visit fortress.Visit, _ fortress.Abort) error { 297 if a.aborted { 298 return fortress.ErrAborted 299 } 300 return visit() 301 } 302 303 type dummyMetricFactory struct { 304 spool.MetricFactory 305 } 306 307 type dummyRecorder struct { 308 spool.MetricRecorder 309 310 // inputs 311 charmURL, unitTag string 312 metrics map[string]corecharm.Metric 313 isDeclaredMetric bool 314 err string 315 316 // outputs 317 closed bool 318 batches []spool.MetricBatch 319 } 320 321 func (r *dummyRecorder) AddMetric( 322 key, value string, created time.Time, labels map[string]string) error { 323 if r.err != "" { 324 return errors.New(r.err) 325 } 326 then := time.Date(2015, 8, 20, 15, 48, 0, 0, time.UTC) 327 r.batches = append(r.batches, spool.MetricBatch{ 328 CharmURL: r.charmURL, 329 UUID: utils.MustNewUUID().String(), 330 Created: then, 331 Metrics: []jujuc.Metric{{ 332 Key: key, 333 Value: value, 334 Time: then, 335 Labels: labels, 336 }}, 337 UnitTag: r.unitTag, 338 }) 339 return nil 340 } 341 342 func (r *dummyRecorder) IsDeclaredMetric(key string) bool { 343 if r.isDeclaredMetric { 344 return true 345 } 346 _, ok := r.metrics[key] 347 return ok 348 } 349 350 func (r *dummyRecorder) Close() error { 351 r.closed = true 352 return nil 353 }