github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/aagent/watchers/nagioswatcher/nagios_test.go (about)

     1  // Copyright (c) 2020-2022, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package nagioswatcher
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/choria-io/go-choria/aagent/model"
    16  	"github.com/golang/mock/gomock"
    17  	. "github.com/onsi/ginkgo/v2"
    18  	. "github.com/onsi/gomega"
    19  
    20  	"github.com/choria-io/go-choria/aagent/util"
    21  	"github.com/choria-io/go-choria/statistics"
    22  )
    23  
    24  func Test(t *testing.T) {
    25  	RegisterFailHandler(Fail)
    26  	RunSpecs(t, "AAgent/Watchers/NagiosWatcher")
    27  }
    28  
    29  var _ = Describe("NagiosWatcher", func() {
    30  	var (
    31  		mockctl     *gomock.Controller
    32  		mockMachine *model.MockMachine
    33  		watch       *Watcher
    34  		now         time.Time
    35  		err         error
    36  		td          string
    37  	)
    38  
    39  	BeforeEach(func() {
    40  		mockctl = gomock.NewController(GinkgoT())
    41  		mockMachine = model.NewMockMachine(mockctl)
    42  
    43  		td, err = os.MkdirTemp("", "")
    44  		Expect(err).ToNot(HaveOccurred())
    45  
    46  		now = time.Unix(1606924953, 0)
    47  		mockMachine.EXPECT().Name().Return("nagios").AnyTimes()
    48  		mockMachine.EXPECT().Identity().Return("ginkgo").AnyTimes()
    49  		mockMachine.EXPECT().InstanceID().Return("1234567890").AnyTimes()
    50  		mockMachine.EXPECT().Version().Return("1.0.0").AnyTimes()
    51  		mockMachine.EXPECT().TimeStampSeconds().Return(now.Unix()).AnyTimes()
    52  		mockMachine.EXPECT().TextFileDirectory().Return(td).AnyTimes()
    53  
    54  		wi, err := New(mockMachine, "ginkgo", []string{"always"}, "fail", "success", "1s", time.Second, map[string]any{
    55  			"plugin": "/bin/sh",
    56  		})
    57  		Expect(err).ToNot(HaveOccurred())
    58  
    59  		watch = wi.(*Watcher)
    60  		watch.previousCheck = now
    61  		watch.previousOutput = "OK: ginkgo"
    62  		watch.previousPerfData = []util.PerfData{}
    63  		watch.previousRunTime = 500 * time.Millisecond
    64  		watch.previous = OK
    65  		watch.previousPlugin = "/bin/sh"
    66  	})
    67  
    68  	AfterEach(func() {
    69  		mockctl.Finish()
    70  		os.RemoveAll(td)
    71  	})
    72  
    73  	Describe("setProperties", func() {
    74  		It("Should parse valid properties", func() {
    75  			watch.properties = nil
    76  			err = watch.setProperties(map[string]any{
    77  				"annotations": map[string]string{
    78  					"a1": "v1",
    79  					"a2": "v2",
    80  				},
    81  				"plugin":  "cmd",
    82  				"timeout": "5s",
    83  			})
    84  			Expect(err).ToNot(HaveOccurred())
    85  			Expect(watch.properties.Annotations).To(Equal(map[string]string{
    86  				"a1": "v1",
    87  				"a2": "v2",
    88  			}))
    89  			Expect(watch.properties.Plugin).To(Equal("cmd"))
    90  			Expect(watch.properties.Timeout).To(Equal(5 * time.Second))
    91  			Expect(watch.properties.Builtin).To(BeEmpty())
    92  			Expect(watch.properties.Gossfile).To(BeEmpty())
    93  		})
    94  
    95  		It("Should handle errors", func() {
    96  			watch.properties = nil
    97  			err = watch.setProperties(map[string]any{})
    98  			Expect(err).To(MatchError("plugin or builtin is required"))
    99  
   100  			watch.properties = nil
   101  			err = watch.setProperties(map[string]any{
   102  				"plugin":  "cmd",
   103  				"builtin": "goss",
   104  			})
   105  			Expect(err).To(MatchError("cannot set plugin and builtin"))
   106  
   107  			watch.properties = nil
   108  			err = watch.setProperties(map[string]any{
   109  				"builtin": "goss",
   110  			})
   111  			Expect(err).To(MatchError("gossfile property is required for the goss builtin check"))
   112  
   113  			watch.properties = nil
   114  			err = watch.setProperties(map[string]any{
   115  				"builtin": "choria_status",
   116  			})
   117  			Expect(err).To(MatchError("last_message property is required for the choria_status builtin check"))
   118  		})
   119  
   120  		It("Should handle valid goss setups", func() {
   121  			watch.properties = nil
   122  			err = watch.setProperties(map[string]any{
   123  				"builtin":  "goss",
   124  				"gossFile": "/x",
   125  			})
   126  			Expect(err).ToNot(HaveOccurred())
   127  			Expect(watch.properties.Builtin).To(Equal("goss"))
   128  			Expect(watch.properties.Gossfile).To(Equal("/x"))
   129  		})
   130  
   131  		It("Should handle valid choria_status setups", func() {
   132  			watch.properties = nil
   133  			err = watch.setProperties(map[string]any{
   134  				"builtin":      "choria_status",
   135  				"last_message": "1h",
   136  			})
   137  			Expect(err).ToNot(HaveOccurred())
   138  			Expect(watch.properties.Builtin).To(Equal("choria_status"))
   139  			Expect(watch.properties.LastMessage).To(Equal(time.Hour))
   140  
   141  			sf := filepath.Join(td, "status.json")
   142  			mockMachine.EXPECT().ChoriaStatusFile().Return(sf, 60*60).AnyTimes()
   143  			state, _, err := watch.watchUsingChoria()
   144  			Expect(state).To(Equal(CRITICAL))
   145  			Expect(err).ToNot(HaveOccurred())
   146  
   147  			now := time.Now().UTC()
   148  			status := statistics.InstanceStatus{
   149  				Identity:        "ginkgo.example.net",
   150  				Uptime:          1000,
   151  				ConnectedServer: "broker.example.net",
   152  				LastMessage:     now.Unix(),
   153  				Provisioning:    false,
   154  				Stats: &statistics.ServerStats{
   155  					Total:      4,
   156  					Valid:      1,
   157  					Invalid:    1,
   158  					Passed:     1,
   159  					Filtered:   1,
   160  					Replies:    2,
   161  					TTLExpired: 1,
   162  					Events:     10,
   163  				},
   164  				CertificateExpires: now.Add(365 * 24 * time.Hour),
   165  				TokenExpires:       now.Add(30 * 24 * time.Hour),
   166  				FileName:           sf,
   167  				ModTime:            now,
   168  			}
   169  			sj, _ := json.Marshal(status)
   170  			os.WriteFile(sf, sj, 0644)
   171  
   172  			state, output, err := watch.watchUsingChoria()
   173  			Expect(output).To(Or(
   174  				Equal(fmt.Sprintf("OK: %s|uptime=1000;; filtered_msgs=1;; invalid_msgs=1;; passed_msgs=1;; replies_msgs=2;; total_msgs=4;; ttlexpired_msgs=1;; last_msg=%d;; cert_expire_seconds=31536000;; token_expire_seconds=2592000;; events=10;;", sf, now.Unix())),
   175  				Equal(fmt.Sprintf("OK: %s|uptime=1000;; filtered_msgs=1;; invalid_msgs=1;; passed_msgs=1;; replies_msgs=2;; total_msgs=4;; ttlexpired_msgs=1;; last_msg=%d;; cert_expire_seconds=31535999;; token_expire_seconds=2591999;; events=10;;", sf, now.Unix())),
   176  			))
   177  			Expect(state).To(Equal(OK))
   178  			Expect(err).ToNot(HaveOccurred())
   179  
   180  			lm := now.Add(-1 * 70 * 70 * time.Second)
   181  			status.LastMessage = lm.Unix()
   182  			sj, _ = json.Marshal(status)
   183  			os.WriteFile(sf, sj, 0644)
   184  
   185  			state, output, err = watch.watchUsingChoria()
   186  			Expect(state).To(Equal(CRITICAL))
   187  			Expect(output).To(Or(
   188  				Equal(fmt.Sprintf("CRITICAL: last message at %s|uptime=1000;; filtered_msgs=1;; invalid_msgs=1;; passed_msgs=1;; replies_msgs=2;; total_msgs=4;; ttlexpired_msgs=1;; last_msg=%d;; cert_expire_seconds=31535999;; token_expire_seconds=2591999;; events=10;;", time.Unix(status.LastMessage, 0).UTC(), status.LastMessage)),
   189  				Equal(fmt.Sprintf("CRITICAL: last message at %s|uptime=1000;; filtered_msgs=1;; invalid_msgs=1;; passed_msgs=1;; replies_msgs=2;; total_msgs=4;; ttlexpired_msgs=1;; last_msg=%d;; cert_expire_seconds=31536000;; token_expire_seconds=2592000;; events=10;;", time.Unix(status.LastMessage, 0).UTC(), status.LastMessage)),
   190  			))
   191  			Expect(err).ToNot(HaveOccurred())
   192  
   193  			watch.properties.CertExpiry = 366 * 24 * time.Hour
   194  			state, output, err = watch.watchUsingChoria()
   195  			Expect(err).ToNot(HaveOccurred())
   196  			Expect(state).To(Equal(CRITICAL))
   197  			Expect(output).To(Or(
   198  				Equal(fmt.Sprintf("CRITICAL: certificate expires %s (8760h0m0s)|uptime=1000;; filtered_msgs=1;; invalid_msgs=1;; passed_msgs=1;; replies_msgs=2;; total_msgs=4;; ttlexpired_msgs=1;; last_msg=%d;; cert_expire_seconds=31535999;; token_expire_seconds=2591999;; events=10;;", status.CertificateExpires, status.LastMessage)),
   199  				Equal(fmt.Sprintf("CRITICAL: certificate expires %s (8760h0m0s)|uptime=1000;; filtered_msgs=1;; invalid_msgs=1;; passed_msgs=1;; replies_msgs=2;; total_msgs=4;; ttlexpired_msgs=1;; last_msg=%d;; cert_expire_seconds=31536000;; token_expire_seconds=2592000;; events=10;;", status.CertificateExpires, status.LastMessage)),
   200  			))
   201  
   202  			watch.properties.CertExpiry = 24 * time.Hour
   203  			watch.properties.TokenExpiry = 365 * 24 * time.Hour
   204  			state, output, err = watch.watchUsingChoria()
   205  			Expect(err).ToNot(HaveOccurred())
   206  			Expect(state).To(Equal(CRITICAL))
   207  			Expect(output).To(Or(
   208  				Equal(fmt.Sprintf("CRITICAL: token expires %s (720h0m0s)|uptime=1000;; filtered_msgs=1;; invalid_msgs=1;; passed_msgs=1;; replies_msgs=2;; total_msgs=4;; ttlexpired_msgs=1;; last_msg=%d;; cert_expire_seconds=31535999;; token_expire_seconds=2591999;; events=10;;", status.TokenExpires, status.LastMessage)),
   209  				Equal(fmt.Sprintf("CRITICAL: token expires %s (720h0m0s)|uptime=1000;; filtered_msgs=1;; invalid_msgs=1;; passed_msgs=1;; replies_msgs=2;; total_msgs=4;; ttlexpired_msgs=1;; last_msg=%d;; cert_expire_seconds=31536000;; token_expire_seconds=2592000;; events=10;;", status.TokenExpires, status.LastMessage)),
   210  			))
   211  
   212  			Expect(err).ToNot(HaveOccurred())
   213  		})
   214  	})
   215  
   216  	Describe("CurrentState", func() {
   217  		It("Should be a valid state", func() {
   218  			cs := watch.CurrentState()
   219  			csj, err := cs.(*StateNotification).JSON()
   220  			Expect(err).ToNot(HaveOccurred())
   221  
   222  			event := map[string]any{}
   223  			err = json.Unmarshal(csj, &event)
   224  			Expect(err).ToNot(HaveOccurred())
   225  			delete(event, "id")
   226  
   227  			Expect(event).To(Equal(map[string]any{
   228  				"time":            "2020-12-02T16:02:33Z",
   229  				"type":            "io.choria.machine.watcher.nagios.v1.state",
   230  				"subject":         "ginkgo",
   231  				"specversion":     "1.0",
   232  				"source":          "io.choria.machine",
   233  				"datacontenttype": "application/json",
   234  				"data": map[string]any{
   235  					"id":          "1234567890",
   236  					"identity":    "ginkgo",
   237  					"machine":     "nagios",
   238  					"name":        "ginkgo",
   239  					"protocol":    "io.choria.machine.watcher.nagios.v1.state",
   240  					"type":        "nagios",
   241  					"version":     "1.0.0",
   242  					"timestamp":   float64(now.Unix()),
   243  					"status_code": float64(0),
   244  					"runtime":     0.5,
   245  					"check_time":  float64(now.Unix()),
   246  					"annotations": map[string]any{},
   247  					"perfdata":    []any{},
   248  					"history":     []any{},
   249  					"status":      "OK",
   250  					"output":      "OK: ginkgo",
   251  					"plugin":      "/bin/sh",
   252  				},
   253  			}))
   254  		})
   255  	})
   256  })