github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/governor/streams/governor_test.go (about)

     1  // Copyright 2020-2022 The NATS Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // Copyright (c) 2022, R.I. Pienaar and the Choria Project contributors
    16  //
    17  // SPDX-License-Identifier: Apache-2.0
    18  
    19  package governor
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"os"
    25  	"strings"
    26  	"sync"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/nats-io/jsm.go"
    31  	natsd "github.com/nats-io/nats-server/v2/server"
    32  	"github.com/nats-io/nats.go"
    33  	. "github.com/onsi/ginkgo/v2"
    34  	. "github.com/onsi/gomega"
    35  )
    36  
    37  func TestGovernor(t *testing.T) {
    38  	RegisterFailHandler(Fail)
    39  	RunSpecs(t, "Provtarget")
    40  }
    41  
    42  func startJSServer() (*natsd.Server, *nats.Conn) {
    43  	d, err := os.MkdirTemp("", "jstest")
    44  	Expect(err).ToNot(HaveOccurred())
    45  
    46  	opts := &natsd.Options{
    47  		ServerName: "test.example.net",
    48  		JetStream:  true,
    49  		StoreDir:   d,
    50  		Port:       -1,
    51  		Host:       "localhost",
    52  		// LogFile:    "/tmp/server.log",
    53  		// Trace:        true,
    54  		// TraceVerbose: true,
    55  		Cluster: natsd.ClusterOpts{Name: "gotest"},
    56  	}
    57  
    58  	s, err := natsd.NewServer(opts)
    59  	Expect(err).ToNot(HaveOccurred())
    60  
    61  	go s.Start()
    62  	if !s.ReadyForConnections(10 * time.Second) {
    63  		Fail("nats server did not start")
    64  	}
    65  
    66  	nc, err := nats.Connect(s.ClientURL(), nats.UseOldRequestStyle(), nats.MaxReconnects(-1))
    67  	Expect(err).ToNot(HaveOccurred())
    68  
    69  	return s, nc
    70  }
    71  
    72  var _ = Describe("Governor", func() {
    73  	It("Should function correctly", func() {
    74  		srv, nc := startJSServer()
    75  		defer srv.Shutdown()
    76  		defer nc.Close()
    77  
    78  		_, err := NewManager("TEST", 0, 0, 0, nc, true)
    79  		Expect(err).To(MatchError("unknown governor"))
    80  
    81  		limit := 100
    82  
    83  		gmgr, err := NewManager("TEST", uint64(limit), 2*time.Minute, 0, nc, true)
    84  		Expect(err).ToNot(HaveOccurred())
    85  
    86  		Expect(gmgr.Name()).To(Equal("TEST"))
    87  		Expect(gmgr.Limit()).To(Equal(int64(limit)))
    88  		Expect(gmgr.MaxAge()).To(Equal(2 * time.Minute))
    89  		Expect(gmgr.Stream().Name()).To(Equal("GOVERNOR_TEST"))
    90  		Expect(gmgr.Stream().Subjects()).To(Equal([]string{"$GOVERNOR.campaign.TEST"}))
    91  
    92  		gmgr, err = NewManager("TEST", uint64(limit), 2*time.Minute, 0, nc, true, WithSubject("$BOB"))
    93  		Expect(err).ToNot(HaveOccurred())
    94  		Expect(gmgr.Stream().Subjects()).To(Equal([]string{"$BOB"}))
    95  
    96  		ts, err := gmgr.LastActive()
    97  		Expect(err).ToNot(HaveOccurred())
    98  
    99  		Expect(ts.IsZero()).To(BeTrue())
   100  
   101  		ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
   102  		defer cancel()
   103  
   104  		workers := 1000
   105  		max := 0
   106  		current := 0
   107  		cnt := 0
   108  		mu := sync.Mutex{}
   109  		wg := sync.WaitGroup{}
   110  		var errs []string
   111  
   112  		// checks evict returns the valid name
   113  		g := New("TEST", nc, WithInterval(10*time.Millisecond), WithSubject("$BOB"))
   114  		fin, seq, err := g.Start(ctx, "testing.eviction")
   115  		Expect(err).ToNot(HaveOccurred())
   116  
   117  		name, err := gmgr.Evict(seq)
   118  		Expect(err).ToNot(HaveOccurred())
   119  		Expect(name).To(Equal("testing.eviction"))
   120  
   121  		_, err = gmgr.Evict(seq)
   122  		Expect(jsm.IsNatsError(err, 10037)).To(BeTrue())
   123  		fin()
   124  
   125  		for i := 0; i < workers; i++ {
   126  			wg.Add(1)
   127  			go func(i int) {
   128  				defer wg.Done()
   129  
   130  				g := New("TEST", nc, WithInterval(10*time.Millisecond), WithSubject("$BOB"))
   131  
   132  				name := fmt.Sprintf("worker %d", i)
   133  				finisher, _, err := g.Start(ctx, name)
   134  				if err != nil {
   135  					mu.Lock()
   136  					errs = append(errs, fmt.Sprintf("%d did not start: %s", i, err))
   137  					mu.Unlock()
   138  					return
   139  				}
   140  
   141  				mu.Lock()
   142  				cnt++
   143  				current++
   144  				if max < current {
   145  					max = current
   146  				}
   147  				mu.Unlock()
   148  
   149  				// give the scheduler a chance
   150  				time.Sleep(50 * time.Millisecond)
   151  
   152  				// before finish because its very quick and another one starts before this happens if its after finished call
   153  				mu.Lock()
   154  				current--
   155  				mu.Unlock()
   156  
   157  				err = finisher()
   158  				if err != nil {
   159  					mu.Lock()
   160  					errs = append(errs, fmt.Sprintf("%d finished failed: %s", i, err))
   161  					mu.Unlock()
   162  					return
   163  				}
   164  
   165  				err = finisher()
   166  				if err != nil {
   167  					mu.Lock()
   168  					errs = append(errs, fmt.Sprintf("2nd finish errored: %s", err))
   169  					mu.Unlock()
   170  					return
   171  				}
   172  			}(i)
   173  		}
   174  
   175  		for {
   176  			if ctx.Err() != nil {
   177  				Fail(fmt.Sprintf("timeout %s", ctx.Err()))
   178  			}
   179  
   180  			mu.Lock()
   181  			if cnt == workers {
   182  				if max > limit {
   183  					Fail(fmt.Sprintf("had more than %d concurrent: %d", limit, max))
   184  				}
   185  				mu.Unlock()
   186  
   187  				wg.Wait()
   188  
   189  				ts, err := gmgr.LastActive()
   190  				Expect(err).ToNot(HaveOccurred())
   191  				Expect(ts).To(BeTemporally("<", time.Now(), time.Second))
   192  
   193  				if len(errs) > 0 {
   194  					Fail(fmt.Sprintf("Had errors in workers: %s", strings.Join(errs, ", ")))
   195  				}
   196  
   197  				return
   198  			}
   199  			mu.Unlock()
   200  
   201  			time.Sleep(time.Millisecond)
   202  		}
   203  	})
   204  })