github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/agent/mcorpc/external/provider_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 external
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	"github.com/golang/mock/gomock"
    15  	. "github.com/onsi/ginkgo/v2"
    16  	. "github.com/onsi/gomega"
    17  	"github.com/onsi/gomega/gbytes"
    18  	"github.com/sirupsen/logrus"
    19  
    20  	"github.com/choria-io/go-choria/build"
    21  	"github.com/choria-io/go-choria/config"
    22  	imock "github.com/choria-io/go-choria/inter/imocks"
    23  	addl "github.com/choria-io/go-choria/providers/agent/mcorpc/ddl/agent"
    24  	"github.com/choria-io/go-choria/server"
    25  	"github.com/choria-io/go-choria/server/agents"
    26  )
    27  
    28  var _ = Describe("McoRPC/External", func() {
    29  	var (
    30  		mockctl *gomock.Controller
    31  		fw      *imock.MockFramework
    32  		mgr     *MockAgentManager
    33  		conn    *imock.MockConnector
    34  		cfg     *config.Config
    35  		prov    *Provider
    36  		ctx     context.Context
    37  		cancel  context.CancelFunc
    38  	)
    39  
    40  	BeforeEach(func() {
    41  		build.TLS = "false"
    42  
    43  		mockctl = gomock.NewController(GinkgoT())
    44  
    45  		buf := gbytes.NewBuffer()
    46  		fw, cfg = imock.NewFrameworkForTests(mockctl, buf)
    47  		fw.EXPECT().Configuration().Return(cfg).AnyTimes()
    48  
    49  		conn = imock.NewMockConnector(mockctl)
    50  
    51  		mgr = NewMockAgentManager(mockctl)
    52  		mgr.EXPECT().Choria().Return(fw).AnyTimes()
    53  		mgr.EXPECT().Logger().Return(fw.Logger("ginkgo")).AnyTimes()
    54  
    55  		lib, err := filepath.Abs("testdata")
    56  		Expect(err).ToNot(HaveOccurred())
    57  
    58  		cfg.Choria.RubyLibdir = []string{lib}
    59  
    60  		prov = &Provider{
    61  			cfg:    cfg,
    62  			log:    fw.Logger("x"),
    63  			agents: []*addl.DDL{},
    64  			paths:  make(map[string]string),
    65  		}
    66  		prov.log.Logger.SetLevel(logrus.DebugLevel)
    67  
    68  		ctx, cancel = context.WithCancel(context.Background())
    69  
    70  		DeferCleanup(func() {
    71  			cancel()
    72  			mockctl.Finish()
    73  		})
    74  	})
    75  
    76  	Describe("reconcileAgents", func() {
    77  		var td string
    78  		var agentDir string
    79  		var err error
    80  
    81  		BeforeEach(func() {
    82  			td, err = os.MkdirTemp("", "")
    83  			Expect(err).ToNot(HaveOccurred())
    84  
    85  			agentDir = filepath.Join(td, "mcollective", "agent")
    86  
    87  			Expect(os.MkdirAll(agentDir, 0700)).To(Succeed())
    88  
    89  			DeferCleanup(func() { os.RemoveAll(td) })
    90  		})
    91  
    92  		copyAgentFile := func(f string) {
    93  			src, err := os.Open(filepath.Join("testdata", "mcollective", "agent", f))
    94  			Expect(err).ToNot(HaveOccurred())
    95  			defer src.Close()
    96  
    97  			dst, err := os.Create(filepath.Join(agentDir, f))
    98  			Expect(err).ToNot(HaveOccurred())
    99  			defer dst.Close()
   100  
   101  			_, err = io.Copy(dst, src)
   102  			Expect(err).ToNot(HaveOccurred())
   103  		}
   104  
   105  		It("Should register new agents", func() {
   106  			mgr.EXPECT().RegisterAgent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(3)
   107  			Expect(prov.agents).To(BeEmpty())
   108  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   109  			Expect(prov.agents).To(HaveLen(3))
   110  		})
   111  
   112  		It("Should upgrade changed agents", func() {
   113  			fileChangeGrace = 0
   114  
   115  			copyAgentFile("one.json")
   116  			prov.cfg.Choria.RubyLibdir = []string{td}
   117  			mgr.EXPECT().RegisterAgent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
   118  
   119  			// load what we have
   120  			Expect(prov.agents).To(BeEmpty())
   121  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   122  			Expect(prov.agents).To(HaveLen(1))
   123  			Expect(prov.paths).To(HaveLen(1))
   124  
   125  			ddlb, err := os.ReadFile(filepath.Join(agentDir, "one.json"))
   126  			Expect(err).ToNot(HaveOccurred())
   127  			ddlb = bytes.Replace(ddlb, []byte(`"version": "5.0.0"`), []byte(`"version": "6.0.0"`), 1)
   128  			Expect(os.WriteFile(filepath.Join(agentDir, "one.json"), ddlb, 0700)).To(Succeed())
   129  
   130  			mgr.EXPECT().ReplaceAgent("one", gomock.Any()).DoAndReturn(func(name string, agent agents.Agent) error {
   131  				Expect(agent.Metadata().Version).To(Equal("6.0.0"))
   132  
   133  				return nil
   134  			}).Times(1)
   135  
   136  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   137  			Expect(prov.agents).To(HaveLen(1))
   138  			Expect(prov.paths).To(HaveLen(1))
   139  			Expect(prov.agents[0].Metadata.Version).To(Equal("6.0.0"))
   140  		})
   141  
   142  		It("Should remove orphaned agents", func() {
   143  			copyAgentFile("one.json")
   144  			copyAgentFile("go_agent.json")
   145  			prov.cfg.Choria.RubyLibdir = []string{td}
   146  			mgr.EXPECT().RegisterAgent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2)
   147  
   148  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   149  			Expect(prov.agents).To(HaveLen(2))
   150  
   151  			Expect(os.Remove(filepath.Join(agentDir, "one.json"))).To(Succeed())
   152  
   153  			mgr.EXPECT().UnregisterAgent("one", conn).Times(1)
   154  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   155  			Expect(prov.agents).To(HaveLen(1))
   156  			Expect(prov.agents[0].Metadata.Name).To(Equal("echo"))
   157  		})
   158  
   159  		It("Should work in sequence", func() {
   160  			fileChangeGrace = 0
   161  			prov.cfg.Choria.RubyLibdir = []string{td}
   162  
   163  			mgr.EXPECT().RegisterAgent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(3)
   164  			mgr.EXPECT().UnregisterAgent(gomock.Any(), conn).Times(2)
   165  			mgr.EXPECT().ReplaceAgent(gomock.Any(), gomock.Any()).Times(1)
   166  
   167  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   168  			Expect(prov.agents).To(BeEmpty())
   169  
   170  			copyAgentFile("one.json")
   171  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   172  			Expect(prov.agents).To(HaveLen(1))
   173  			Expect(prov.agents[0].Metadata.Name).To(Equal("one"))
   174  
   175  			copyAgentFile("go_agent.json")
   176  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   177  			Expect(prov.agents).To(HaveLen(2))
   178  			Expect(prov.agents[0].Metadata.Name).To(Equal("one"))
   179  			Expect(prov.agents[1].Metadata.Name).To(Equal("echo"))
   180  
   181  			Expect(os.Remove(filepath.Join(agentDir, "one.json"))).To(Succeed())
   182  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   183  			Expect(prov.agents).To(HaveLen(1))
   184  			Expect(prov.agents[0].Metadata.Name).To(Equal("echo"))
   185  
   186  			ddlb, err := os.ReadFile(filepath.Join(agentDir, "go_agent.json"))
   187  			Expect(err).ToNot(HaveOccurred())
   188  			ddlb = bytes.Replace(ddlb, []byte(`"version": "1.0.0"`), []byte(`"version": "6.0.0"`), 1)
   189  			Expect(os.WriteFile(filepath.Join(agentDir, "go_agent.json"), ddlb, 0700)).To(Succeed())
   190  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   191  			Expect(prov.agents).To(HaveLen(1))
   192  			Expect(prov.agents[0].Metadata.Name).To(Equal("echo"))
   193  			Expect(prov.agents[0].Metadata.Version).To(Equal("6.0.0"))
   194  
   195  			copyAgentFile("one.json")
   196  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   197  			Expect(prov.agents).To(HaveLen(2))
   198  			Expect(prov.agents[0].Metadata.Name).To(Equal("echo"))
   199  			Expect(prov.agents[1].Metadata.Name).To(Equal("one"))
   200  
   201  			Expect(os.Remove(filepath.Join(agentDir, "go_agent.json"))).To(Succeed())
   202  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   203  			Expect(prov.agents).To(HaveLen(1))
   204  			Expect(prov.agents[0].Metadata.Name).To(Equal("one"))
   205  		})
   206  	})
   207  
   208  	Describe("Agents", func() {
   209  		It("Should return all the agent ddls", func() {
   210  			mgr.EXPECT().RegisterAgent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
   211  			Expect(prov.reconcileAgents(ctx, mgr, conn)).To(Succeed())
   212  
   213  			agents := prov.Agents()
   214  			Expect(prov.agents).To(HaveLen(3))
   215  			Expect(prov.agents[0].Metadata.Name).To(Equal("echo"))
   216  			Expect(prov.agents[1].Metadata.Name).To(Equal("one"))
   217  			Expect(prov.agents[2].Metadata.Name).To(Equal("three"))
   218  			Expect(agents).To(Equal(prov.agents))
   219  
   220  			Expect(prov.paths).To(HaveLen(3))
   221  		})
   222  	})
   223  
   224  	Describe("shouldLoadAgent", func() {
   225  		It("Should correctly allow or deny agents", func() {
   226  			Expect(shouldLoadAgent("choria_util")).To(BeFalse())
   227  			Expect(shouldLoadAgent("ginkgo")).To(BeTrue())
   228  		})
   229  	})
   230  
   231  	Describe("Plugin", func() {
   232  		It("Should be a valid AgentProvider", func() {
   233  			p := server.AgentProvider(prov)
   234  			Expect(p).ToNot(BeNil())
   235  		})
   236  	})
   237  })