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  })