github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/lifecycle/tally/tally_test.go (about)

     1  // Copyright (c) 2020-2021, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package tally
     6  
     7  import (
     8  	"io"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/choria-io/go-choria/lifecycle"
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	dto "github.com/prometheus/client_model/go"
    15  
    16  	. "github.com/onsi/ginkgo/v2"
    17  	. "github.com/onsi/gomega"
    18  	"github.com/sirupsen/logrus"
    19  )
    20  
    21  func TestChoria(t *testing.T) {
    22  	RegisterFailHandler(Fail)
    23  	RunSpecs(t, "Lifecycle/Tally")
    24  }
    25  
    26  var _ = Describe("Tally", func() {
    27  	var (
    28  		logger   = logrus.NewEntry(logrus.New())
    29  		recorder *Recorder
    30  	)
    31  
    32  	BeforeEach(func() {
    33  		logger.Logger.SetOutput(io.Discard)
    34  		registerStats = false
    35  		recorder = &Recorder{
    36  			active:   1,
    37  			observed: make(map[string]*observations),
    38  			options: &options{
    39  				Component:  "ginkgo",
    40  				StatPrefix: "tally",
    41  				Log:        logger,
    42  			},
    43  		}
    44  		recorder.createStats()
    45  	})
    46  
    47  	Describe("maintenance", func() {
    48  		It("Should not delete current nodes", func() {
    49  			event, err := lifecycle.New(lifecycle.Alive, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.3"), lifecycle.Identity("ginkgo.example.net"))
    50  			Expect(err).ToNot(HaveOccurred())
    51  
    52  			recorder.process(event)
    53  			Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(1))
    54  			recorder.maintenance()
    55  			Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(1))
    56  		})
    57  
    58  		It("Should delete old nodes", func() {
    59  			event, err := lifecycle.New(lifecycle.Alive, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.3"), lifecycle.Identity("ginkgo.example.net"))
    60  			Expect(err).ToNot(HaveOccurred())
    61  
    62  			recorder.process(event)
    63  			Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(1))
    64  
    65  			recorder.observed["ginkgo"].hosts["ginkgo.example.net"].ts = recorder.observed["ginkgo"].hosts["ginkgo.example.net"].ts.Add(-1 * (60 * time.Minute))
    66  			recorder.maintenance()
    67  			Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(1))
    68  
    69  			recorder.observed["ginkgo"].hosts["ginkgo.example.net"].ts = recorder.observed["ginkgo"].hosts["ginkgo.example.net"].ts.Add(-1 * (90 * time.Minute))
    70  			recorder.maintenance()
    71  			Expect(recorder.observed["ginkgo"].hosts).To(BeEmpty())
    72  		})
    73  	})
    74  
    75  	Describe("elections", func() {
    76  		It("Should correctly label metrics", func() {
    77  			event, err := lifecycle.New(lifecycle.Startup, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.3"), lifecycle.Identity("ginkgo.example.net"))
    78  			Expect(err).ToNot(HaveOccurred())
    79  
    80  			recorder.process(event)
    81  			Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(1))
    82  
    83  			Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3", "1")).To(Equal(1.0))
    84  
    85  			recorder.lostCb()
    86  
    87  			event, err = lifecycle.New(lifecycle.Startup, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.4"), lifecycle.Identity("other.example.net"))
    88  			Expect(err).ToNot(HaveOccurred())
    89  
    90  			recorder.process(event)
    91  			Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(2))
    92  
    93  			Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3", "1")).To(Equal(1.0))
    94  			Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.4", "0")).To(Equal(1.0))
    95  
    96  			recorder.wonCb()
    97  			event, err = lifecycle.New(lifecycle.Startup, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.4"), lifecycle.Identity("foo.example.net"))
    98  			Expect(err).ToNot(HaveOccurred())
    99  
   100  			recorder.process(event)
   101  			Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(3))
   102  
   103  			Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3", "1")).To(Equal(1.0))
   104  			Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.4", "0")).To(Equal(1.0))
   105  			Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.4", "1")).To(Equal(1.0))
   106  		})
   107  	})
   108  
   109  	Describe("process", func() {
   110  		Describe("Shutdown Events", func() {
   111  			It("Should handle existing nodes", func() {
   112  				event, err := lifecycle.New(lifecycle.Startup, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.3"), lifecycle.Identity("ginkgo.example.net"))
   113  				Expect(err).ToNot(HaveOccurred())
   114  
   115  				recorder.process(event)
   116  				Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(1))
   117  
   118  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3", "1")).To(Equal(1.0))
   119  
   120  				event, err = lifecycle.New(lifecycle.Shutdown, lifecycle.Component("ginkgo"), lifecycle.Identity("ginkgo.example.net"))
   121  				Expect(err).ToNot(HaveOccurred())
   122  				recorder.process(event)
   123  
   124  				Expect(recorder.observed["ginkgo"].hosts).To(BeEmpty())
   125  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3")).To(Equal(0.0))
   126  			})
   127  
   128  			It("Should handle new nodes", func() {
   129  				event, err := lifecycle.New(lifecycle.Shutdown, lifecycle.Component("ginkgo"), lifecycle.Identity("ginkgo.example.net"))
   130  				Expect(err).ToNot(HaveOccurred())
   131  				recorder.process(event)
   132  
   133  				Expect(recorder.observed).To(BeEmpty())
   134  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3")).To(Equal(0.0))
   135  			})
   136  		})
   137  
   138  		Describe("Startup Events", func() {
   139  			It("Should handle new nodes", func() {
   140  				event, err := lifecycle.New(lifecycle.Startup, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.3"), lifecycle.Identity("ginkgo.example.net"))
   141  				Expect(err).ToNot(HaveOccurred())
   142  
   143  				Expect(recorder.observed).To(BeEmpty())
   144  				recorder.process(event)
   145  				Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(1))
   146  
   147  				Expect(recorder.observed["ginkgo"].hosts["ginkgo.example.net"].version).To(Equal("1.2.3"))
   148  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3", "1")).To(Equal(1.0))
   149  			})
   150  
   151  			It("Should handle existing nodes", func() {
   152  				event, err := lifecycle.New(lifecycle.Startup, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.3"), lifecycle.Identity("ginkgo.example.net"))
   153  				Expect(err).ToNot(HaveOccurred())
   154  
   155  				Expect(recorder.observed).To(BeEmpty())
   156  				recorder.process(event)
   157  				Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(1))
   158  
   159  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3", "1")).To(Equal(1.0))
   160  
   161  				event, err = lifecycle.New(lifecycle.Startup, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.4"), lifecycle.Identity("ginkgo.example.net"))
   162  				Expect(err).ToNot(HaveOccurred())
   163  
   164  				recorder.process(event)
   165  
   166  				Expect(recorder.observed["ginkgo"].hosts["ginkgo.example.net"].version).To(Equal("1.2.4"))
   167  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3", "1")).To(Equal(0.0))
   168  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.4", "1")).To(Equal(1.0))
   169  			})
   170  		})
   171  
   172  		Describe("Governor Events", func() {
   173  			It("Should handle governor events", func() {
   174  				event, err := lifecycle.New(lifecycle.Governor, lifecycle.Component("ginkgo"), lifecycle.GovernorName("GINKGO"), lifecycle.GovernorType(lifecycle.GovernorEnterEvent))
   175  				Expect(err).ToNot(HaveOccurred())
   176  				recorder.process(event)
   177  				Expect(getPromCountValue(recorder.governorEvents, "ginkgo", "GINKGO", "enter", "1")).To(Equal(1.0))
   178  				Expect(getPromCountValue(recorder.governorEvents, "ginkgo", "GINKGO", "exit", "1")).To(Equal(0.0))
   179  
   180  				event, err = lifecycle.New(lifecycle.Governor, lifecycle.Component("ginkgo"), lifecycle.GovernorName("GINKGO"), lifecycle.GovernorType(lifecycle.GovernorExitEvent))
   181  				Expect(err).ToNot(HaveOccurred())
   182  				recorder.process(event)
   183  				Expect(getPromCountValue(recorder.governorEvents, "ginkgo", "GINKGO", "enter", "1")).To(Equal(1.0))
   184  				Expect(getPromCountValue(recorder.governorEvents, "ginkgo", "GINKGO", "exit", "1")).To(Equal(1.0))
   185  			})
   186  		})
   187  
   188  		Describe("Alive Events", func() {
   189  			It("Should handle new hosts", func() {
   190  				event, err := lifecycle.New(lifecycle.Alive, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.3"), lifecycle.Identity("ginkgo.example.net"))
   191  				Expect(err).ToNot(HaveOccurred())
   192  
   193  				Expect(recorder.observed).To(BeEmpty())
   194  				recorder.process(event)
   195  				Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(1))
   196  
   197  				Expect(recorder.observed["ginkgo"].hosts["ginkgo.example.net"].version).To(Equal("1.2.3"))
   198  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3", "1")).To(Equal(1.0))
   199  			})
   200  
   201  			It("Should handle old hosts", func() {
   202  				event, err := lifecycle.New(lifecycle.Alive, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.3"), lifecycle.Identity("ginkgo.example.net"))
   203  				Expect(err).ToNot(HaveOccurred())
   204  
   205  				recorder.process(event)
   206  				Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(1))
   207  				Expect(recorder.observed["ginkgo"].hosts["ginkgo.example.net"].version).To(Equal("1.2.3"))
   208  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3", "1")).To(Equal(1.0))
   209  
   210  				recorder.observed["ginkgo"].hosts["ginkgo.example.net"].ts = time.Now().Add(-120 * time.Minute)
   211  
   212  				recorder.process(event)
   213  
   214  				Expect(recorder.observed["ginkgo"].hosts).To(HaveLen(1))
   215  				Expect(recorder.observed["ginkgo"].hosts["ginkgo.example.net"].version).To(Equal("1.2.3"))
   216  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3", "1")).To(Equal(1.0))
   217  				Expect(recorder.observed["ginkgo"].hosts["ginkgo.example.net"].ts).To(BeTemporally("~", time.Now(), time.Second))
   218  			})
   219  
   220  			It("Should handle updated hosts", func() {
   221  				event, err := lifecycle.New(lifecycle.Alive, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.3"), lifecycle.Identity("ginkgo.example.net"))
   222  				Expect(err).ToNot(HaveOccurred())
   223  
   224  				recorder.process(event)
   225  				Expect(recorder.observed["ginkgo"].hosts["ginkgo.example.net"].version).To(Equal("1.2.3"))
   226  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.3", "1")).To(Equal(1.0))
   227  
   228  				event, err = lifecycle.New(lifecycle.Alive, lifecycle.Component("ginkgo"), lifecycle.Version("1.2.4"), lifecycle.Identity("ginkgo.example.net"))
   229  				Expect(err).ToNot(HaveOccurred())
   230  
   231  				recorder.process(event)
   232  				Expect(recorder.observed["ginkgo"].hosts["ginkgo.example.net"].version).To(Equal("1.2.4"))
   233  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.2", "1")).To(Equal(0.0))
   234  				Expect(getPromGaugeValue(recorder.versionsTally, "ginkgo", "1.2.4", "1")).To(Equal(1.0))
   235  			})
   236  		})
   237  	})
   238  })
   239  
   240  func getPromCountValue(ctr *prometheus.CounterVec, labels ...string) float64 {
   241  	pb := &dto.Metric{}
   242  	m, err := ctr.GetMetricWithLabelValues(labels...)
   243  	if err != nil {
   244  		return 0
   245  	}
   246  
   247  	if m.Write(pb) != nil {
   248  		return 0
   249  	}
   250  
   251  	return pb.GetCounter().GetValue()
   252  }
   253  
   254  func getPromGaugeValue(ctr *prometheus.GaugeVec, labels ...string) float64 {
   255  	pb := &dto.Metric{}
   256  	m, err := ctr.GetMetricWithLabelValues(labels...)
   257  	if err != nil {
   258  		return 0
   259  	}
   260  
   261  	if m.Write(pb) != nil {
   262  		return 0
   263  	}
   264  
   265  	return pb.GetGauge().GetValue()
   266  }