github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/aagent/watchers/metricwatcher/metric_test.go (about) 1 // Copyright (c) 2020-2024, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package metricwatcher 6 7 import ( 8 "context" 9 "encoding/json" 10 "os" 11 "path/filepath" 12 "runtime" 13 "testing" 14 "time" 15 16 "github.com/choria-io/go-choria/aagent/model" 17 "github.com/golang/mock/gomock" 18 . "github.com/onsi/ginkgo/v2" 19 . "github.com/onsi/gomega" 20 ) 21 22 func Test(t *testing.T) { 23 RegisterFailHandler(Fail) 24 RunSpecs(t, "AAgent/Watchers/MetricWatcher") 25 } 26 27 var _ = Describe("MetricWatcher", func() { 28 var ( 29 mockctl *gomock.Controller 30 mockMachine *model.MockMachine 31 watch *Watcher 32 now time.Time 33 td string 34 err error 35 ) 36 37 BeforeEach(func() { 38 mockctl = gomock.NewController(GinkgoT()) 39 mockMachine = model.NewMockMachine(mockctl) 40 41 td, err = os.MkdirTemp("", "") 42 Expect(err).ToNot(HaveOccurred()) 43 44 now = time.Unix(1606924953, 0) 45 mockMachine.EXPECT().Name().Return("metric").AnyTimes() 46 mockMachine.EXPECT().Identity().Return("ginkgo").AnyTimes() 47 mockMachine.EXPECT().InstanceID().Return("1234567890").AnyTimes() 48 mockMachine.EXPECT().Version().Return("1.0.0").AnyTimes() 49 mockMachine.EXPECT().TimeStampSeconds().Return(now.Unix()).AnyTimes() 50 mockMachine.EXPECT().Infof("ginkgo", gomock.Any(), gomock.Any()).AnyTimes() 51 mockMachine.EXPECT().Debugf("ginkgo", gomock.Any(), gomock.Any()).AnyTimes() 52 mockMachine.EXPECT().Directory().Return(".").AnyTimes() 53 mockMachine.EXPECT().TextFileDirectory().Return(td).AnyTimes() 54 mockMachine.EXPECT().State().Return("run").AnyTimes() 55 56 wi, err := New(mockMachine, "ginkgo", []string{"run"}, "fail", "success", "", time.Second, map[string]any{ 57 "command": "metric.sh", 58 }) 59 Expect(err).ToNot(HaveOccurred()) 60 watch = wi.(*Watcher) 61 }) 62 63 AfterEach(func() { 64 mockctl.Finish() 65 os.Remove(td) 66 }) 67 68 Describe("performWatch", func() { 69 It("Should run the script and correctly parse nagios style metrics", func() { 70 if runtime.GOOS == "windows" { 71 Skip("not tested on windows yet") 72 } 73 74 handled := false 75 mockMachine.EXPECT().NotifyWatcherState("ginkgo", gomock.Any()).Do(func(_ string, m *StateNotification) { 76 Expect(m.Metrics.Labels).To(Equal(map[string]string{ 77 "dupe": "w", 78 "format": "nagios", 79 })) 80 Expect(m.Metrics.Metrics["failed_events"]).To(Equal(0.0)) 81 Expect(m.Metrics.Metrics["failed_resources"]).To(Equal(0.0)) 82 Expect(m.Metrics.Metrics["last_run_duration"]).To(Equal(59.67)) 83 Expect(m.Metrics.Metrics["time_since_last_run"]).To(Equal(237.0)) 84 Expect(m.Metrics.Metrics["choria_runtime_seconds"]).To(BeNumerically(">", 0)) 85 handled = true 86 }) 87 88 wi, err := New(mockMachine, "ginkgo", []string{"run"}, "fail", "success", "", time.Second, map[string]any{ 89 "command": filepath.Join("testdata", "nagios.sh"), 90 "labels": map[string]string{"dupe": "w"}, 91 }) 92 Expect(err).ToNot(HaveOccurred()) 93 watch = wi.(*Watcher) 94 95 ctx, cancel := context.WithCancel(context.Background()) 96 defer cancel() 97 98 watch.performWatch(ctx) 99 Expect(handled).To(BeTrue()) 100 }) 101 102 It("Should run the script and correctly parse choria style metrics", func() { 103 if runtime.GOOS == "windows" { 104 Skip("not tested on windows yet") 105 } 106 107 handled := false 108 mockMachine.EXPECT().NotifyWatcherState("ginkgo", gomock.Any()).Do(func(_ string, m *StateNotification) { 109 Expect(m.Metrics.Labels).To(Equal(map[string]string{ 110 "dupe": "w", 111 "unique": "u", 112 "format": "choria", 113 })) 114 Expect(m.Metrics.Metrics["v1"]).To(Equal(float64(1))) 115 Expect(m.Metrics.Metrics["v2"]).To(Equal(1.1)) 116 Expect(m.Metrics.Metrics["choria_runtime_seconds"]).To(BeNumerically(">", 0)) 117 handled = true 118 }) 119 120 wi, err := New(mockMachine, "ginkgo", []string{"run"}, "fail", "success", "", time.Second, map[string]any{ 121 "command": filepath.Join("testdata", "metric.sh"), 122 "labels": map[string]string{"dupe": "w"}, 123 }) 124 Expect(err).ToNot(HaveOccurred()) 125 watch = wi.(*Watcher) 126 127 ctx, cancel := context.WithCancel(context.Background()) 128 defer cancel() 129 130 watch.performWatch(ctx) 131 Expect(handled).To(BeTrue()) 132 }) 133 }) 134 135 Describe("setProperties", func() { 136 It("Should parse valid properties", func() { 137 err := watch.setProperties(map[string]any{ 138 "command": "cmd", 139 "interval": "1s", 140 "labels": map[string]string{ 141 "test": "label", 142 }, 143 }) 144 Expect(err).ToNot(HaveOccurred()) 145 Expect(watch.properties.Command).To(Equal("cmd")) 146 Expect(watch.properties.Interval).To(Equal(time.Second)) 147 Expect(watch.properties.Labels).To(Equal(map[string]string{"test": "label"})) 148 }) 149 150 It("Should handle errors", func() { 151 watch.properties = nil 152 err = watch.setProperties(map[string]any{ 153 "interval": "500ms", 154 }) 155 Expect(err).To(MatchError("command is required")) 156 }) 157 158 It("Should enforce 1 second intervals", func() { 159 err := watch.setProperties(map[string]any{ 160 "command": "cmd", 161 "interval": "500ms", 162 }) 163 Expect(err).ToNot(HaveOccurred()) 164 Expect(watch.properties.Command).To(Equal("cmd")) 165 Expect(watch.properties.Interval).To(Equal(time.Second)) 166 }) 167 }) 168 169 Describe("CurrentState", func() { 170 It("Should be a valid state", func() { 171 watch.previousRunTime = 500 * time.Millisecond 172 cs := watch.CurrentState() 173 csj, err := cs.(*StateNotification).JSON() 174 Expect(err).ToNot(HaveOccurred()) 175 176 event := map[string]any{} 177 err = json.Unmarshal(csj, &event) 178 Expect(err).ToNot(HaveOccurred()) 179 delete(event, "id") 180 181 Expect(event).To(Equal(map[string]any{ 182 "time": "2020-12-02T16:02:33Z", 183 "type": "io.choria.machine.watcher.metric.v1.state", 184 "subject": "ginkgo", 185 "specversion": "1.0", 186 "source": "io.choria.machine", 187 "datacontenttype": "application/json", 188 "data": map[string]any{ 189 "id": "1234567890", 190 "identity": "ginkgo", 191 "machine": "metric", 192 "name": "ginkgo", 193 "protocol": "io.choria.machine.watcher.metric.v1.state", 194 "type": "metric", 195 "version": "1.0.0", 196 "timestamp": float64(now.Unix()), 197 "metrics": map[string]any{ 198 "labels": map[string]any{}, 199 "metrics": map[string]any{ 200 "choria_runtime_seconds": 0.5, 201 }, 202 "time": float64(0), 203 }, 204 }, 205 })) 206 }) 207 }) 208 })