github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/agent/mcorpc/client/options_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 client
     6  
     7  import (
     8  	"fmt"
     9  	"time"
    10  
    11  	"github.com/choria-io/go-choria/inter"
    12  	imock "github.com/choria-io/go-choria/inter/imocks"
    13  	"github.com/choria-io/go-choria/message"
    14  	"github.com/golang/mock/gomock"
    15  	. "github.com/onsi/ginkgo/v2"
    16  	. "github.com/onsi/gomega"
    17  
    18  	"github.com/choria-io/go-choria/protocol"
    19  	"github.com/choria-io/go-choria/providers/agent/mcorpc/ddl/agent"
    20  )
    21  
    22  var _ = Describe("McoRPC/Client/Options", func() {
    23  	var (
    24  		mockctl *gomock.Controller
    25  		o       *RequestOptions
    26  		fw      *imock.MockFramework
    27  		err     error
    28  	)
    29  
    30  	BeforeEach(func() {
    31  		mockctl = gomock.NewController(GinkgoT())
    32  		fw, _ = imock.NewFrameworkForTests(mockctl, GinkgoWriter, imock.WithCallerID())
    33  
    34  		ddl, _ := agent.FindLocally("package", []string{"testdata"})
    35  		o, err = NewRequestOptions(fw, ddl)
    36  		Expect(err).ToNot(HaveOccurred())
    37  	})
    38  
    39  	AfterEach(func() {
    40  		mockctl.Finish()
    41  	})
    42  
    43  	Describe("ConfigureMessage", func() {
    44  		It("Should configure the message", func() {
    45  			msg, err := message.NewMessage(nil, "test", "mcollective", "request", nil, fw)
    46  			Expect(err).ToNot(HaveOccurred())
    47  
    48  			Targets([]string{"host1", "host2"})(o)
    49  			BroadcastRequest()(o)
    50  
    51  			err = o.ConfigureMessage(msg)
    52  			Expect(err).ToNot(HaveOccurred())
    53  
    54  			Expect(msg.DiscoveredHosts()).To(Equal([]string{"host1", "host2"}))
    55  			Expect(o.Targets).To(Equal([]string{"host1", "host2"}))
    56  			Expect(msg.Type()).To(Equal("request"))
    57  			Expect(o.ReplyTo).To(Equal(msg.ReplyTo()))
    58  			Expect(o.ProcessReplies).To(BeTrue())
    59  			Expect(o.totalStats.discoveredNodes).To(Equal([]string{"host1", "host2"}))
    60  			Expect(o.totalStats.RequestID).To(Equal(msg.RequestID()))
    61  			Expect(o.RequestID).To(Equal(msg.RequestID()))
    62  		})
    63  
    64  		It("Should support the message supplying targets", func() {
    65  			msg, err := message.NewMessage(nil, "test", "mcollective", "request", nil, fw)
    66  			Expect(err).ToNot(HaveOccurred())
    67  			msg.SetDiscoveredHosts([]string{"host1", "host2"})
    68  
    69  			o.ConfigureMessage(msg)
    70  
    71  			Expect(o.Targets).To(Equal([]string{"host1", "host2"}))
    72  		})
    73  
    74  		It("Should support custom reply targets", func() {
    75  			msg, err := message.NewMessage(nil, "test", "mcollective", "request", nil, fw)
    76  			Expect(err).ToNot(HaveOccurred())
    77  
    78  			Targets([]string{"host1", "host2"})(o)
    79  			ReplyTo("test.target")(o)
    80  
    81  			o.ConfigureMessage(msg)
    82  
    83  			Expect(msg.ReplyTo()).To(Equal("test.target"))
    84  			Expect(o.ReplyTo).To(Equal(msg.ReplyTo()))
    85  			Expect(o.ProcessReplies).To(BeFalse())
    86  		})
    87  
    88  		It("Should support limiting targets", func() {
    89  			targets := make([]string, 100)
    90  			for i := 0; i < 100; i++ {
    91  				targets[i] = fmt.Sprintf("target%d", i)
    92  			}
    93  
    94  			msg, err := message.NewMessage(nil, "test", "mcollective", "request", nil, fw)
    95  			Expect(err).ToNot(HaveOccurred())
    96  
    97  			Targets(targets)(o)
    98  			LimitMethod("first")(o)
    99  			LimitSize("2")(o)
   100  			o.ConfigureMessage(msg)
   101  			Expect(o.Targets).To(Equal([]string{"target0", "target1"}))
   102  			Expect(o.totalStats.discoveredNodes).To(Equal([]string{"target0", "target1"}))
   103  		})
   104  
   105  		It("Should support cached transports", func() {
   106  			targets := make([]string, 100)
   107  			for i := 0; i < 100; i++ {
   108  				targets[i] = fmt.Sprintf("target%d", i)
   109  			}
   110  
   111  			msg, err := message.NewMessage(nil, "test", "mcollective", "request", nil, fw)
   112  			Expect(err).ToNot(HaveOccurred())
   113  
   114  			msg.CacheTransport()
   115  			Targets(targets)(o)
   116  			InBatches(10, 30)(o)
   117  			DiscoveryTimeout(2 * time.Second)(o)
   118  			Timeout(20 * time.Second)(o)
   119  			msg.SetTTL(10)
   120  
   121  			err = o.ConfigureMessage(msg)
   122  			Expect(err).ToNot(HaveOccurred())
   123  
   124  			expected := 10 * (10 + 2 + 20)
   125  			Expect(msg.TTL()).To(Equal(expected))
   126  		})
   127  
   128  		It("Should limit cached TTL to 5 hours", func() {
   129  			targets := make([]string, 100)
   130  			for i := 0; i < 100; i++ {
   131  				targets[i] = fmt.Sprintf("target%d", i)
   132  			}
   133  
   134  			msg, err := message.NewMessage(nil, "test", "mcollective", "request", nil, fw)
   135  			Expect(err).ToNot(HaveOccurred())
   136  
   137  			msg.CacheTransport()
   138  			Targets(targets)(o)
   139  			InBatches(10, 30)(o)
   140  			DiscoveryTimeout(2 * time.Second)(o)
   141  			Timeout(20 * time.Second)(o)
   142  			msg.SetTTL(int(6 * time.Hour.Seconds()))
   143  
   144  			err = o.ConfigureMessage(msg)
   145  			Expect(err).To(MatchError("cached transport TTL is unreasonably long"))
   146  		})
   147  
   148  		It("Should support service requests", func() {
   149  			msg, err := message.NewMessage(nil, "test", "mcollective", "request", nil, fw)
   150  			Expect(err).ToNot(HaveOccurred())
   151  
   152  			msg.CacheTransport()
   153  			ServiceRequest()(o)
   154  			err = o.ConfigureMessage(msg)
   155  			Expect(err).ToNot(HaveOccurred())
   156  
   157  			Expect(msg.Type()).To(Equal(inter.ServiceRequestMessageType))
   158  			Expect(msg.Filter().Empty()).To(BeTrue())
   159  			Expect(msg.DiscoveredHosts()).To(BeEmpty())
   160  		})
   161  	})
   162  
   163  	Describe("NewRequestOptions", func() {
   164  		It("Should create correct new options", func() {
   165  			Expect(o.ProtocolVersion).To(Equal(protocol.RequestV1))
   166  			Expect(o.RequestType).To(Equal("direct_request"))
   167  			Expect(o.Collective).To(Equal("ginkgo"))
   168  			Expect(o.ProcessReplies).To(BeTrue())
   169  			Expect(o.Timeout).To(Equal(time.Duration(182) * time.Second))
   170  			Expect(o.fw).To(Equal(fw))
   171  			Expect(o.LimitSeed).To(BeNumerically(">", 0))
   172  			Expect(o.LimitMethod).To(Equal("first"))
   173  		})
   174  	})
   175  
   176  	Describe("Stats", func() {
   177  		It("Should return the stats", func() {
   178  			Expect(o.Stats()).To(Equal(o.totalStats))
   179  		})
   180  	})
   181  
   182  	Describe("Targets", func() {
   183  		It("Should set the targets", func() {
   184  			Targets([]string{"host1"})(o)
   185  			Expect(o.Targets).To(Equal([]string{"host1"}))
   186  		})
   187  	})
   188  
   189  	Describe("Protocol", func() {
   190  		It("Should set the protocol to use", func() {
   191  			Protocol(protocol.RequestV1)(o)
   192  			Expect(o.ProtocolVersion).To(Equal(protocol.RequestV1))
   193  		})
   194  	})
   195  
   196  	Describe("DirectRequest", func() {
   197  		It("Should set the type", func() {
   198  			DirectRequest()(o)
   199  			Expect(o.RequestType).To(Equal("direct_request"))
   200  		})
   201  	})
   202  
   203  	Describe("BroadcastRequest", func() {
   204  		It("Should set the type", func() {
   205  			BroadcastRequest()(o)
   206  			Expect(o.RequestType).To(Equal("request"))
   207  		})
   208  	})
   209  
   210  	Describe("Workers", func() {
   211  		It("Should set the workers", func() {
   212  			Workers(10)(o)
   213  			Expect(o.Workers).To(Equal(10))
   214  		})
   215  	})
   216  
   217  	Describe("Collective", func() {
   218  		It("Should set the collective", func() {
   219  			Collective("bob")(o)
   220  			Expect(o.Collective).To(Equal("bob"))
   221  		})
   222  	})
   223  
   224  	Describe("ReplyTo", func() {
   225  		It("Should set the target", func() {
   226  			ReplyTo("bob")(o)
   227  			Expect(o.ReplyTo).To(Equal("bob"))
   228  		})
   229  	})
   230  
   231  	Describe("InBatches", func() {
   232  		It("Should set the size, batched mode and sleep", func() {
   233  			InBatches(10, 5)(o)
   234  			Expect(o.BatchSize).To(Equal(10))
   235  			Expect(o.BatchSleep).To(Equal(5 * time.Second))
   236  		})
   237  	})
   238  
   239  	Describe("Replies", func() {
   240  		It("Should set the channel and disable the handlers", func() {
   241  			Replies(make(chan inter.ConnectorMessage, 123))(o)
   242  			Expect(o.Replies).To(HaveCap(123))
   243  		})
   244  	})
   245  
   246  	Describe("Timeout", func() {
   247  		It("Should set the timeout", func() {
   248  			Timeout(10 * time.Second)(o)
   249  			Expect(o.Timeout).To(Equal(10 * time.Second))
   250  		})
   251  	})
   252  
   253  	Describe("ReplyHandler", func() {
   254  		It("Should set the handler", func() {
   255  			seen := false
   256  
   257  			ReplyHandler(func(p protocol.Reply, r *RPCReply) { seen = true })(o)
   258  
   259  			o.Handler(nil, nil)
   260  
   261  			Expect(seen).To(BeTrue())
   262  		})
   263  	})
   264  
   265  	Describe("ConnectionName", func() {
   266  		It("Should set the name", func() {
   267  			ConnectionName("ginkgo")(o)
   268  			Expect(o.ConnectionName).To(Equal("ginkgo"))
   269  		})
   270  	})
   271  
   272  	Describe("LimitMethod", func() {
   273  		It("Should set the method", func() {
   274  			LimitMethod("random")(o)
   275  			Expect(o.LimitMethod).To(Equal("random"))
   276  		})
   277  	})
   278  
   279  	Describe("LimitSize", func() {
   280  		It("Should set the size", func() {
   281  			LimitSize("10%")(o)
   282  			Expect(o.LimitSize).To(Equal("10%"))
   283  		})
   284  	})
   285  
   286  	Describe("DiscoveryStartCB", func() {
   287  		It("Should set the cb", func() {
   288  			called := false
   289  			cb := func() { called = true }
   290  			DiscoveryStartCB(cb)(o)
   291  			o.DiscoveryStartCB()
   292  			Expect(called).To(BeTrue())
   293  		})
   294  	})
   295  
   296  	Describe("DiscoveryStartCB", func() {
   297  		It("Should set the cb", func() {
   298  			called := false
   299  			cb := func(_, _ int) error { called = true; return nil }
   300  
   301  			DiscoveryEndCB(cb)(o)
   302  			o.DiscoveryEndCB(0, 0)
   303  
   304  			Expect(called).To(BeTrue())
   305  		})
   306  	})
   307  
   308  	Describe("LimitSeed", func() {
   309  		It("Should set the seed", func() {
   310  			LimitSeed(100)(o)
   311  			Expect(o.LimitSeed).To(Equal((int64(100))))
   312  		})
   313  	})
   314  
   315  	Describe("limitTargets", func() {
   316  		var targets []string
   317  
   318  		BeforeEach(func() {
   319  			targets = make([]string, 100)
   320  			for i := 0; i < 100; i++ {
   321  				targets[i] = fmt.Sprintf("target%d", i)
   322  			}
   323  		})
   324  
   325  		It("Should not limit to 0", func() {
   326  			o.LimitSize = "0"
   327  			_, err := o.limitTargets(targets)
   328  			Expect(err).To(MatchError("no targets left after applying target limits of '0'"))
   329  		})
   330  
   331  		It("Should accept only valid methods", func() {
   332  			o.LimitMethod = "broken"
   333  			l, err := o.limitTargets(targets)
   334  			Expect(err).To(MatchError("limit method 'broken' is not valid, only 'random' or 'first' supported"))
   335  			Expect(l).To(HaveLen(100))
   336  		})
   337  
   338  		It("Should return the supplied targets unshuffled when limit size is not set", func() {
   339  			o.LimitSize = ""
   340  			o.LimitMethod = "random"
   341  			l, err := o.limitTargets(targets)
   342  			Expect(err).ToNot(HaveOccurred())
   343  			Expect(l).To(HaveLen(100))
   344  			Expect(targets[0]).To(Equal("target0"))
   345  			Expect(targets[20]).To(Equal("target20"))
   346  			Expect(targets[30]).To(Equal("target30"))
   347  			Expect(targets[40]).To(Equal("target40"))
   348  			Expect(targets[50]).To(Equal("target50"))
   349  			Expect(targets[99]).To(Equal("target99"))
   350  		})
   351  
   352  		It("Should limit to specific size and optionally shuffle the targets", func() {
   353  			o.LimitSize = "5"
   354  			o.LimitMethod = "first"
   355  			l, err := o.limitTargets(targets)
   356  			Expect(err).ToNot(HaveOccurred())
   357  			Expect(l).To(HaveLen(5))
   358  			Expect(l).To(Equal([]string{"target0", "target1", "target2", "target3", "target4"}))
   359  
   360  			o.LimitMethod = "random"
   361  			o.LimitSeed = 1
   362  			l, err = o.limitTargets(targets)
   363  			Expect(err).ToNot(HaveOccurred())
   364  			Expect(l).To(HaveLen(5))
   365  			Expect(l).To(Equal([]string{"target19", "target26", "target0", "target73", "target94"}))
   366  		})
   367  
   368  		It("Should limit to specific percentage and optionally shuffle the targets", func() {
   369  			o.LimitSize = "5%"
   370  			o.LimitMethod = "first"
   371  			l, err := o.limitTargets(targets)
   372  			Expect(err).ToNot(HaveOccurred())
   373  			Expect(l).To(HaveLen(5))
   374  			Expect(l).To(Equal(targets[0:5]))
   375  
   376  			o.LimitMethod = "random"
   377  			o.LimitSeed = 1
   378  			l, err = o.limitTargets(targets)
   379  			Expect(err).ToNot(HaveOccurred())
   380  			Expect(l).To(HaveLen(5))
   381  			Expect(l).To(Equal([]string{"target19", "target26", "target0", "target73", "target94"}))
   382  		})
   383  	})
   384  
   385  	Describe("shuffleLimitedTargets", func() {
   386  		var targets []string
   387  
   388  		BeforeEach(func() {
   389  			targets = make([]string, 100)
   390  			for i := 0; i < 100; i++ {
   391  				targets[i] = fmt.Sprintf("target%d", i)
   392  			}
   393  		})
   394  
   395  		It("Should support not shuffling non random method targets", func() {
   396  			o.LimitMethod = "first"
   397  			o.shuffleLimitedTargets(targets)
   398  			Expect(targets).To(HaveLen(100))
   399  			Expect(targets[0]).To(Equal("target0"))
   400  			Expect(targets[20]).To(Equal("target20"))
   401  			Expect(targets[30]).To(Equal("target30"))
   402  			Expect(targets[40]).To(Equal("target40"))
   403  			Expect(targets[50]).To(Equal("target50"))
   404  			Expect(targets[99]).To(Equal("target99"))
   405  		})
   406  
   407  		It("Should shuffle random method targets", FlakeAttempts(3), func() {
   408  			o.LimitMethod = "random"
   409  			o.LimitSeed = -1
   410  			o.shuffleLimitedTargets(targets)
   411  			Expect(targets).To(HaveLen(100))
   412  			// small chance of failure here if random shuffling leaves these 2 in place
   413  			for i := 0; i < 10; i++ {
   414  				if targets[0] == "target0" || targets[99] == "target99" {
   415  					o.shuffleLimitedTargets(targets)
   416  				} else {
   417  					break
   418  				}
   419  			}
   420  			Expect(targets[0]).ToNot(Equal("target0"))
   421  			Expect(targets[99]).ToNot(Equal("target99"))
   422  			Expect(targets).To(HaveLen(100))
   423  		})
   424  
   425  		It("Should support seeds", func() {
   426  			o.LimitMethod = "random"
   427  			o.LimitSeed = 1
   428  			o.shuffleLimitedTargets(targets)
   429  			Expect(targets).To(HaveLen(100))
   430  			Expect(targets[0]).To(Equal("target19"))
   431  			Expect(targets[1]).To(Equal("target26"))
   432  			Expect(targets[2]).To(Equal("target0"))
   433  			Expect(targets[3]).To(Equal("target73"))
   434  			Expect(targets).To(HaveLen(100))
   435  		})
   436  	})
   437  })