github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/metrics/collect/handler_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package collect_test 5 6 import ( 7 "net" 8 "os" 9 "path/filepath" 10 "strings" 11 "time" 12 13 corecharm "github.com/juju/charm/v12" 14 "github.com/juju/clock" 15 "github.com/juju/loggo" 16 "github.com/juju/names/v5" 17 "github.com/juju/testing" 18 jc "github.com/juju/testing/checkers" 19 "github.com/juju/worker/v3/dependency" 20 dt "github.com/juju/worker/v3/dependency/testing" 21 "github.com/juju/worker/v3/workertest" 22 gc "gopkg.in/check.v1" 23 24 coretesting "github.com/juju/juju/testing" 25 "github.com/juju/juju/worker/metrics/collect" 26 "github.com/juju/juju/worker/metrics/spool" 27 "github.com/juju/juju/worker/uniter/runner/context" 28 ) 29 30 type handlerSuite struct { 31 coretesting.BaseSuite 32 33 manifoldConfig collect.ManifoldConfig 34 manifold dependency.Manifold 35 dataDir string 36 resources dt.StubResources 37 recorder *dummyRecorder 38 listener *mockListener 39 mockReadCharm *mockReadCharm 40 } 41 42 var _ = gc.Suite(&handlerSuite{}) 43 44 func (s *handlerSuite) SetUpTest(c *gc.C) { 45 s.BaseSuite.SetUpTest(c) 46 s.manifoldConfig = collect.ManifoldConfig{ 47 AgentName: "agent-name", 48 MetricSpoolName: "metric-spool-name", 49 CharmDirName: "charmdir-name", 50 Clock: clock.WallClock, 51 Logger: loggo.GetLogger("test"), 52 } 53 s.manifold = collect.Manifold(s.manifoldConfig) 54 s.dataDir = c.MkDir() 55 56 // create unit agent base dir so that hooks can run. 57 err := os.MkdirAll(filepath.Join(s.dataDir, "agents", "unit-u-0"), 0777) 58 c.Assert(err, jc.ErrorIsNil) 59 60 s.recorder = &dummyRecorder{ 61 charmURL: "local:trusty/metered-1", 62 unitTag: "metered/0", 63 metrics: map[string]corecharm.Metric{ 64 "pings": { 65 Description: "test metric", 66 Type: corecharm.MetricTypeAbsolute, 67 }, 68 "juju-units": {}, 69 }, 70 } 71 72 s.resources = dt.StubResources{ 73 "agent-name": dt.NewStubResource(&dummyAgent{dataDir: s.dataDir}), 74 "metric-spool-name": dt.NewStubResource(&mockMetricFactory{recorder: s.recorder}), 75 "charmdir-name": dt.NewStubResource(&dummyCharmdir{aborted: false}), 76 } 77 78 s.PatchValue(collect.NewRecorder, 79 func(_ names.UnitTag, _ context.Paths, _ spool.MetricFactory) (spool.MetricRecorder, error) { 80 // Return a dummyRecorder here, because otherwise a real one 81 // *might* get instantiated and error out, if the periodic worker 82 // happens to fire before the worker shuts down (as seen in 83 // LP:#1497355). 84 return &dummyRecorder{ 85 charmURL: "local:trusty/metered-1", 86 unitTag: "metered/0", 87 metrics: map[string]corecharm.Metric{ 88 "pings": { 89 Description: "test metric", 90 Type: corecharm.MetricTypeAbsolute, 91 }, 92 "juju-units": {}, 93 }, 94 }, nil 95 }, 96 ) 97 s.mockReadCharm = &mockReadCharm{} 98 s.PatchValue(collect.ReadCharm, s.mockReadCharm.ReadCharm) 99 s.listener = &mockListener{} 100 s.PatchValue(collect.NewSocketListener, collect.NewSocketListenerFnc(s.listener)) 101 } 102 103 func (s *handlerSuite) TestListenerStart(c *gc.C) { 104 worker, err := s.manifold.Start(s.resources.Context()) 105 c.Assert(err, jc.ErrorIsNil) 106 c.Assert(worker, gc.NotNil) 107 c.Assert(s.listener.Calls(), gc.HasLen, 0) 108 workertest.CleanKill(c, worker) 109 s.listener.CheckCall(c, 0, "Stop") 110 } 111 112 func (s *handlerSuite) TestJujuUnitsBuiltinMetric(c *gc.C) { 113 worker, err := s.manifold.Start(s.resources.Context()) 114 c.Assert(err, jc.ErrorIsNil) 115 c.Assert(worker, gc.NotNil) 116 c.Assert(s.listener.Calls(), gc.HasLen, 0) 117 118 conn, err := s.listener.trigger() 119 c.Assert(err, jc.ErrorIsNil) 120 conn.CheckCallNames(c, "SetDeadline", "Write", "Close") 121 122 responseString := strings.Trim(string(conn.data), " \n\t") 123 c.Assert(responseString, gc.Equals, "ok") 124 c.Assert(s.recorder.batches, gc.HasLen, 1) 125 126 workertest.CleanKill(c, worker) 127 s.listener.CheckCall(c, 0, "Stop") 128 } 129 130 func (s *handlerSuite) TestReadCharmCalledOnEachTrigger(c *gc.C) { 131 worker, err := s.manifold.Start(s.resources.Context()) 132 c.Assert(err, jc.ErrorIsNil) 133 c.Assert(worker, gc.NotNil) 134 c.Assert(s.listener.Calls(), gc.HasLen, 0) 135 136 _, err = s.listener.trigger() 137 c.Assert(err, jc.ErrorIsNil) 138 _, err = s.listener.trigger() 139 c.Assert(err, jc.ErrorIsNil) 140 141 s.PatchValue(collect.ReadCharm, s.mockReadCharm.ReadCharm) 142 workertest.CleanKill(c, worker) 143 144 // Expect 3 calls to ReadCharm, one on start and one per handler call 145 s.mockReadCharm.CheckCallNames(c, "ReadCharm", "ReadCharm", "ReadCharm") 146 s.listener.CheckCall(c, 0, "Stop") 147 } 148 149 func (s *handlerSuite) TestHandlerError(c *gc.C) { 150 worker, err := s.manifold.Start(s.resources.Context()) 151 c.Assert(err, jc.ErrorIsNil) 152 c.Assert(worker, gc.NotNil) 153 c.Assert(s.listener.Calls(), gc.HasLen, 0) 154 155 s.recorder.err = "well, this is embarrassing" 156 157 conn, err := s.listener.trigger() 158 c.Assert(err, gc.ErrorMatches, "failed to collect metrics: error adding 'juju-units' metric: well, this is embarrassing") 159 conn.CheckCallNames(c, "SetDeadline", "Write", "Close") 160 161 responseString := strings.Trim(string(conn.data), " \n\t") 162 //c.Assert(responseString, gc.Matches, ".*well, this is embarrassing") 163 c.Assert(responseString, gc.Equals, `error: failed to collect metrics: error adding 'juju-units' metric: well, this is embarrassing`) 164 c.Assert(s.recorder.batches, gc.HasLen, 0) 165 166 workertest.CleanKill(c, worker) 167 s.listener.CheckCall(c, 0, "Stop") 168 } 169 170 type mockListener struct { 171 testing.Stub 172 handler spool.ConnectionHandler 173 } 174 175 func (l *mockListener) trigger() (*mockConnection, error) { 176 conn := &mockConnection{} 177 dying := make(chan struct{}) 178 err := l.handler.Handle(conn, dying) 179 if err != nil { 180 return conn, err 181 } 182 return conn, nil 183 } 184 185 // Stop implements the stopper interface. 186 func (l *mockListener) Stop() error { 187 l.AddCall("Stop") 188 return nil 189 } 190 191 func (l *mockListener) SetHandler(handler spool.ConnectionHandler) { 192 l.handler = handler 193 } 194 195 type mockConnection struct { 196 net.Conn 197 testing.Stub 198 data []byte 199 } 200 201 // SetDeadline implements the net.Conn interface. 202 func (c *mockConnection) SetDeadline(t time.Time) error { 203 c.AddCall("SetDeadline", t) 204 return nil 205 } 206 207 // Write implements the net.Conn interface. 208 func (c *mockConnection) Write(data []byte) (int, error) { 209 c.AddCall("Write", data) 210 c.data = make([]byte, len(data)) 211 copy(c.data, data) 212 return len(data), nil 213 } 214 215 // Close implements the net.Conn interface. 216 func (c *mockConnection) Close() error { 217 c.AddCall("Close") 218 return nil 219 } 220 221 type mockMetricFactory struct { 222 spool.MetricFactory 223 recorder *dummyRecorder 224 } 225 226 // Recorder implements the spool.MetricFactory interface. 227 func (f *mockMetricFactory) Recorder(metrics map[string]corecharm.Metric, charmURL, unitTag string) (spool.MetricRecorder, error) { 228 return f.recorder, nil 229 } 230 231 type mockReadCharm struct { 232 testing.Stub 233 } 234 235 func (m *mockReadCharm) ReadCharm(unitTag names.UnitTag, paths context.Paths) (string, map[string]corecharm.Metric, error) { 236 m.MethodCall(m, "ReadCharm", unitTag, paths) 237 return "local:trusty/metered-1", 238 map[string]corecharm.Metric{ 239 "pings": {Description: "test metric", Type: corecharm.MetricTypeAbsolute}, 240 "juju-units": {}, 241 }, nil 242 }