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